Patrick Meenan

Welcome!

This is my personal blog and mostly contains my random thoughts about web performance, development, browsers or whatever else I might be thinking about at the time.

Latest Posts

Introducing Waterfall Tools

I’ve been wanting to build a 100% client-based waterfall tool for a long time. Something with a much more modern rendering engine and UI than WebPageTest’s server-side php-based image waterfalls. It’s a fairly big project though and I never had the time to invest into it but that all changed with the advance of the AI-assisted development tools (I refuse to use the term “vibe coding” given the amount of actual engineering that went into it).

Today, I bring you Waterfall Tools!

Waterfall
Waterfall

What is it?

Waterfall Tools is a JS library that provides the necessary logic for ingesting a HUGE variety of data formats, extracts the waterfall page and request data, and provides interfaces for rendering it using canvas into a container that you provide. It provides hooks for interacting with the waterfall (hover, click and request data) so you can build a full viewer.

It ALSO provides a full viewer that can be embedded in an iFrame or loaded directly with query params that allow you to control the behavior and provide a source URL for the data to be rendered.

As a stand-alone viewer, it can work completely client-side and you can drag and drop an appropriate file onto the viewer and it will provide a rich view of the details of the request data, a WebPageTest-style waterfall and integration with other tools like Perfetto and Chrome’s netlog viewer, all within the client UI.

It can run completely offline with service worker support.

If the source data is loaded from a URL, the resulting waterfall view is sharable, down to the specific view you are looking at.

It supports multiple pages within a single data source (e.g. a WebPageTest agent run with multiple pages, or a HAR file with multiple pages).

The canvas-rendered waterfalls can still be copy/pasted or directly saved as images so they can still easily be dropped into documents and presentations.

Page Selection screen
Page Selection screen

What formats does it support?

Waterfall Tools supports every format I could think of to implement and supports adding more as needed. The current list includes:

  • WebPageTest agent (wptagent) raw test results (sample)

  • WebPageTest JSON (sample)

  • WebPageTest HAR files (including those from the HTTP Archive) (sample)

  • Chrome Netlog captures (sample)

  • Chrome Trace capture (in both perfetto protobuf and JSON formats) (sample)

  • Chrome HAR files (sample)

  • Firefox HAR files (sample)

  • Raw tcpdump captures (with keylog files for TLS decryption) (sample)

I’m particularly excited about the tcpdump support. It handles:

  • Decoding the capture files
  • Building the TCP and UDP streams
  • Decrypting TLS traffic using the keylog file (including QUIC TLS1.3)
  • Extracting HTTP/1.x, HTTP/2 and HTTP/3 requests and responses (including HPACK and QPACK decoding)
  • Decoding DNS traffic
  • Decoding DNS over HTTPS (DoH) traffic
  • Decoding DNS over TLS (DoT) traffic
  • Extracting response bodies
  • Decompressing gzip, zstd and br content-encodings

All in 100% vanilla javascript leveraging browser APIs where possible.

Request Details

Clicking on any request in the waterfall will open (or focus) a closable tab with the request details that you’re used to seeing in WebPageTest’s pop-up dialogs.

Request details
Request details (image)

Including syntax-highlighted response bodies for text-based content types.

Request details
Request details (javascript)

Embedded Viewers

Beyond the directly-owned UI for rendering the waterfalls and request data, the viewer also integrates with other tools for relevant formats depending on what the test data includes.

For example, if the data source is a Chrome netlog or the test data includes a captured netlog, the viewer provides a “Netlog” tab that embeds the Chrome Netlog Viewer to provide a rich view of the network traffic.

Embedded Netlog viewer
Embedded Netlog viewer

Similarly, if the data source is a Chrome trace or the test data includes a captured trace, the viewer provides a “Trace” tab that embeds the Perfetto UI to provide a rich view of the trace data.

Embedded Perfetto viewer
Embedded Perfetto viewer

If the test data includes lighthouse results, the viewer embeds the lighthouse result in a “Lighthouse” tab.

Embedded Lighthouse viewer
Embedded Lighthouse viewer

Waterfall customization

The waterfall rendering is highly configurable and you can control all of the things you are used to being able to control in WebPageTest’s waterfall viewer and then some.

Waterfall Options
Waterfall Options

I’m particularly happy that we can now, FINALLY, adjust the start time of the waterfall to zoom in on a section in the middle of a waterfall. This is something that has been requested for years for WebPageTest and I’m glad we can provide it here.

The connection view and data charts below the waterfall are also supported including request-specific details as you hover over or click on the individual chunks in the connection view.

Connection View
Connection View

How big is it?

I made the conscious decision to focus on a vanilla javascript project that requires “modern” browsers and went with a full javascript application including a lot of the UI. A lot of the core functionality requires it anyway and it wouldn’t make sense to try to jump through hoops to use native browser elements and css for things like the waterfall rendering.

The core library is split into 3 pieces (all sizes are reported in compressed wire-sizes):

  • 40 kB : The core waterfall tools JS (which includes support for parsing all of the formats except tcpdump and the canvas rendering engine)
  • 17 kB : The tcpdump parser including all of the decryption and decoding logic and everything except for JS-based Brotli decompression.
  • 59 kB : Javascript-based Brotli decompression (for browsers that don’t support DecompressionStream('brotli'))

The tcpdump and Brotli support are loaded on-demand as-needed (and hopefully the Brotli support can go away entirely over time as browsers add native DecompressionStream support).

The Viewer UI has a few pieces between the HTML, CSS, JS and logo image. All-in, that comes in at around 25 kB for the viewer UI.

The viewer bundles the Chrome Netlog viewer if you want to be able to view netlogs in the native viewer and it is a bit of a pig, relatively speaking at 170 kB.

For a core Waterfall client supporting everything except for tcpdump import and without built-in netlog viewing, that comes in at around 65 kB.

I’m sure it will grow over time as more features are added but I’m very happy with that for the level of functionality it provides.

Free and Open Source

The project is as unburdened with licensing as possible. The code and all of it’s dependencies are under Apache 2, MIT, BSD or equivalent licenses, allowing you to do anything you want with it (including commercially).

You can find the repository here: https://github.com/pmeenan/waterfall-tools

Please file issues for anything you see that is broken or that you’d like added or changed and contributions are welcome.

The project is designed to be developed with the help of AI coding assistants with a running set of agent instructions in AGENTS.md (and referenced from CLAUDE.md) so they should be picked up automatically.

The viewer is available at https://waterfall-tools.com and you can play with it right now with the samples linked above or by dragging and dropping your own files onto the page.

What’s Next?

There are still a lot of features I’d like to add and I’m sure there are a lot of edge-case bugs that still need to be fixed (what is there now is the result of about a week’s worth of weekends and evenings).

Some of the things on my TODO list include:

  • Filmstrip view (this is a big one)
  • “All images” view that shows all of the images that were loaded and any optimization opportunities
  • Console log in the Summary tab
  • More metrics extracted from the tcpdump, netlog and Chrome trace files

The results viewing is also just a lego piece in a full testing pipeline. I’d like to see if I can connect the viewer directly to local test tooling like wptagent’s CLI, Puppeteer, Playwright, Crossbench, etc. so that you can run tests locally and view the results in the viewer without having to upload files to a server.

It could also be interesting to hook up to a simple test queuing system that runs tests on remote infrastructure, using something like the old WebPageTest API to submit jobs and get results back.

If you chain that with persistent storage somewhere, you basically have a full synthetic testing pipeline with swappable pieces.

Shipping jQuery and React Frameworks with Chrome

Should we ship jQuery, React and other popular frameworks with browsers so sites don’t have to re-download the same frameworks over and over?

Some background

For years, web performance advocates have casually suggested that browsers should “just ship jQuery” or other popular frameworks to avoid the need for every site to force users to re-download identical library code (there’s a recent WHATWG discussion on it here).

However, this concept has historically faced several fundamental hurdles.

First, there is a massive variety of framework versions in active use across the web, making it almost impossible to select a single “canonical” version.

Second, sites must be able to react quickly to security vulnerabilities, and being “locked” to a browser-shipped version could seriously hinder necessary updates.

Finally, these frameworks are served from a wide array of domains and are frequently bundled with site-specific code, which completely breaks simple URL-based caching.

Proposal: A Web-Wide Compression Dictionary

Compression dictionary transport brings an interesting possible solution to this problem. Instead of shipping raw library binaries, we could ship a versioned compression dictionary (e.g., a “2026 web” dictionary) that includes common frameworks like React and jQuery. This is basically the modern alternative to the old “Built-In Web Libraries” approach.

Unlike the whole bundling approach—which struggled with the sheer variety of library versions and the risk of making certain versions “sticky” and slowing down security updates—a compression dictionary provides a wonderfully transparent mechanism. It allows servers to compress their unique resource bundles against the shared dictionary, gaining cross-site sharing benefits without requiring developers to change their HTML or worrying about being locked into a specific binary version. The dictionary natively supports versioning and avoids the privacy risks or other concerns associated with traditional shared library caching schemes.

Since libraries tend to change pretty incrementally over time, a single version of jQuery, React, or other commonly used code can actually compress other versions of the same library really well, eliminating the need to match a site’s specific version.

Even better, the proposal leverages the existing Compression Dictionary Transport mechanism and the Available-Dictionary request header for seamless backward compatibility and easy deployment. The browser would just advertise the web-wide dictionary as being available when a better, content-specific dictionary is not.

Methodology: Building the Dictionary

So how do we build it? The 50MB dictionary was constructed by analyzing massive amounts of public web data pulled as part of the HTTP Archive crawl. For this run, the crawl was updated to parse all of the Javascript it encountered, extract each top-level comment and function block, and store them in the crawls_staging.script_chunks table along with the hash of the payload and the URL it was served from.

(The code for generating and testing the dictionary is up on Github in the web-dictionary project).

We counted the unique occurrences of those hashes across different URLs and pulled the script chunks that were seen on at least 10,000 different URLs. That yielded around 10,300 highly pervasive script or comment blocks. A deduplication pass was then used to ensure that similar functions—such as those across different versions of the same library—were compressed against each other. This was purely to minimize the dictionary size while maximizing utility. The resulting dictionary is around 50MB and works with both Brotli and ZStandard.

The tested dictionary contains a lot of the typical boilerplate copyright blocks as well as the frameworks you’d normally expect to see out there (jQuery, jQueryUI, React, Preact, Angular, etc.), plus a lot of underlying code that is widely reused.

Methodology: Testing the Dictionary

To actually test the effectiveness of this beast, I pulled the list of script and HTML requests that were loaded by the top 100,000 pages from the March HTTP Archive crawl. That resulted in ~3 million unique URLs.

I then fetched the URLs independently to keep BigQuery costs in check (even though the HTTP Archive has the original bodies) and re-compressed them twice: once with Brotli level 11, and once with Brotli level 11 plus the 50MB dictionary. The original encoded size as-served from the origin was also logged, and then the relative sizes were compared for analysis.

Experimental Results

I stopped the processing at ~400k URLs (70% scripts, 25% HTML) because the data converged really quickly and wasn’t changing as more URLs were processed.

Here’s how the savings looked:

Bar chart of framework dictionary compression savings
Framework Dictionary Compression Savings

Script Metrics

  • Brotli 11 Savings over Original: saved 16% (11 KB).
  • Brotli 11 + Dict Savings over Original: saved 29% (15 KB).
  • Brotli 11 + Dict Savings over Brotli 11: saved 15% (4 KB).

HTML Metrics

  • Brotli 11 Savings over Original: saved 35% (8 KB).
  • Brotli 11 + Dict Savings over Original: saved 55% (9.5 KB).
  • Brotli 11 + Dict Savings over Brotli 11: saved 27% (1.7 KB).

Conclusion

While the inclusion of a 50MB framework dictionary does offer some really great compression benefits—particularly for HTML and certain classes of scripts—the overall conclusion is that it’s just not yet worth the effort right now.

Managing a 50MB static dictionary on every single client device and growing adoption of server-side compression with the same dictionary is a fairly long and drawn-out process.

Given that just using Brotli 11 compression alone already provides significant savings over what most websites are currently serving (and tuning their compression is exactly what we’d have to do for the dictionary support anyway), the most effective path forward is to encourage broader adoption of Brotli 11 well before we start introducing the overhead of a browser-shipped web-wide dictionary.

Does Gemini Create Fast Websites?

I’ve used AI to generate a few websites over the last month or so and not too long before that I was helping my son create a website on Wix so I thought it might be interesting to see how they stack up against each other in terms of performance. I was REALLY motivated when I saw the new Stitch app had a “create react app” button (ok, petrified probably describes my reaction better than motivated).

The contenders

The first site I helped with was a Wix website for my son’s horse transport business. I directed him there since he could build most of it himself and I knew the Wix team works hard to make their sites fast (within the constraints of running a giant self-publishing framework). We spent a fair bit of time on getting the layout to “mostly work” but the grid system is pretty limiting so we ended up with a design that was “good enough” rather than “great”. He did most of the work though so I’m happy with that part of things.

The next site was a site that I promised a contractor that we use a lot that I could help him put together. I’ve been meaning to play with 11ty and other frameworks for a while but the level of effort has always been more than I wanted to take on and I have zero design skills (less than zero if that’s possible). I figured it was a good time to see how far AI has come since I last used it with Gemini 3.0 (which didn’t go great but was decent for spec work) and both Gemini 3.1 and Antigravity had just come out so it felt like the perfect time to give it a shot.

This was a breath of fresh air and probably the best experience I’ve ever had with a dev tool for building websites. I basically just told it the pages that I wanted to include, some details about the business, the features I wanted the website to have and that I wanted it to be statically hosted (and preferably served from cloudflare pages). It built a beautiful, functional site in just a few minutes. Some of the things didn’t work quite right but I just told it what needed to be fixed and it took care of it. It had used some stock photos served from third-party sites as placeholders and I had it use Nano Banana Pro to generate better ones (and to self-host them) and had it generate photo galleries using actual photos he provided me.

I had to ask it to automate resizing and optimizing of the images but, to this day, I haven’t had to touch a line of the source code for the site. I was even lazy enough to ask the AI to change some text on the page instead of doing it myself. It created a nicely responsive site that performed well and took just a few hours of effort from end to end. It picked Astro as the framework to use and it may need some more tweaks to fix things as they come up (like I haven’t checked how accessible it is yet) but it’s WAY better than I could have done on my own.

The third site I built was to migrate this blog off of blogger (again, with Antigravity and Gemini 3.1 pro thinking). I won’t get into all of the details because I already wrote a post about it but this time I was more specific about wanting it to generate a fast SSG website that defaults to plain HTML and only uses JS as a progressive enhancement. I asked it to build it so that I could author articles in markdown and for it to migrate all of the existing content.

Again, a few hours of work produced something I’m really happy with and is way better than I could have done by myself. I’ve had to go back a few times to ask it to add features (like OG meta tags for post embedding, an RSS feed, etc) but that was all done through prompts. I write all of the markdown files for the articles by hand but I haven’t touched a line of the framework code that turns it into the blog (Astro again).

The final one I wanted to look at was a quick test with using Stitch to generate a react app. I spent maybe five minutes on it because it’s just for testing purposes and I asked it to scrape the content from the contractor website and redesign it. I’m not the target audience for the tool so I didn’t spend any time on the design itself of the functionality of the app other than to make sure it worked because I was mostly interested in seeing how the code it generated performed.

How did they do?

I was really impressed with both of the Astro sites that Antigravity generated. They handily beat the performance of the Wix and Stitch-generated sites and served plain HTML with a side of progressive enhancement.

Side-by-side filmstrip of the four websites loading
Side-by-side filmstrip of the four websites loading

To be fair, they aren’t all serving the same content and the Wix page has a massive background image but they’re relatively comparable and the waterfalls clearly show that the performance differences aren’t because of the content.

Wix

The Wix site is surprisingly fast given how much it loads but even for the above-the-fold content it is pulling resources from 4 different domains and relying on a fair bit of JS:

Wix waterfall
Wix waterfall

That is the trimmed-down version just to get to the LCP image. The full waterfall is 174 requests and 2MB of content, most of which is Javascript (11MB uncompressed JS).

Antigravity - Contractor website

This, I was thrilled with:

Antigravity contractor waterfall
Antigravity contractor waterfall

It looks like it loads ~600 bytes of JS to help with email address decoding (from the mailto: link I assume) and pulls fonts from Google fonts but, otherwise it’s just the HTML, CSS and images for the content itself. ~200KB all-in and 9 requests. Most of that is from the fonts and images. At some point I should have it inline that javascript and self-host the fonts but I’m thrilled with the result.

Antigravity take two - My blog

I picked an article page with a visible image to make it fair(er) but it knocked it out of the park:

Antigravity blog waterfall
Antigravity blog waterfall

No javascript, no custom fonts, just the HTML, CSS and images for the content itself. The one thing that did jump out at me was the late loading of the images including the hero image. It looks like Astro defaults to loading="lazy" on all images so I just went back and asked Gemini to fix that.

My first attempt was a little too heavy-handed and I removed lazy loading from ALL images which made things a bit worse since it would load all of the images in parallel (though, at least starting sooner):

Antigravity blog waterfall - take two
Antigravity blog waterfall - take two

The third time was the charm:

Antigravity blog waterfall - take three
Antigravity blog waterfall - take three

Now it loads the first image in any blog post eagerly, in parallel with the CSS, and then lets native lazy-loading take care of any subsequent article images.

The visual experience isn’t meaningfully different since it still has to go through the layout/style/render pipeline before displaying it to the screen but the browser now has what it needs to display the above-the-fold content ~40% sooner (and I feel better about myself).

Stitch react app

Yeah, it’s about what I expected. See if you can tell when the app hydrates:

Stitch waterfall
Stitch waterfall

It doesn’t actually display anything on the screen until well after the waterfall finishes (while the client-side rendering is being applied). It doesn’t help that every one of the react requests is redirecting from a generic major version to a specific patch version. The actual weight of the JS isn’t as bad as I worried it might be (11 requests, ~625KB compressed) but it’s ALL render blocking.

Here’s hoping people take the export options of the MCP or project brief and feed it into something else to build the actual websites and don’t try going straight to production with the built-in react app. It’s great for getting a feel for how the app would work but not much else.

Conclusion

I’m really excited for where this journey is taking us. I think the ease-of-use for generating websites directly instead of using a CMS is going to be a game-changer and it will be interesting to see how the market evolves.

That said, the tools still need a lot of help and are “tools”. They remind me of a conversation I had with a product manager years ago about their site performance. He had assumed that the dev team would “just make it fast” and that it wasn’t something he should have asked them to focus on. It feels like AI is at a similar place and our role is basically the same. It CAN make things fast, responsive, accessible, etc., but it might not unless you specifically tell it to.

View All Posts in Archive
Enlarged view