94% Faster Website Builds
I got my site’s builds on Cloudflare from 26.5 mins to 1.5 mins.
I am thrilled. These glacier speed builds were bugging me for years. Adding in repo cloning, install, and deployment time, the total build time was sometimes hitting 30+ mins and timing out. It was only going to get worse with more photos.
I was able to achieve the speedup by:
- Reducing image size variants
- Updating libraries
- Copying original files directly
- Caching the build
1. Fewer image size variants
This first one is a bit of a lie. I did reduce the number of images, but it didn’t help much with speed. I’m still glad I did though.
I was generating multiple sizes of each image for srcset/sizes. I halved widths through the first number < 500px. For the most common width of 2504px, this meant making four variants for each image:
- 2504px
- 1252px
- 626px
- 318px
I realized this was way more than needed. While tiny devices like the iPhone SE may display full-width pictures at a narrow CSS width like 343px after page margins, they have a device pixel ratio of 2x, meaning they’d want a 686px width image or larger. This means both the smallest two sizes in that list (626px and 318px) are likely never being requested.
I changed the logic to only halve while the smallest size would be at least 704px wide. (I.e., while width >= 1408px, halve it.) This is a little arbitrary, but 704px is my content width, so seemed like a decent number not to go below. In practice this means most images will now only have two variants.
This cut my number of images from ~8,500 to ~4,500. Nice for staving off Cloudflare Pages’ 20k file limit, but unfortunately, it only gave about a 1.5 minute build speedup! (26.5 mins → 25 mins.) My untested hypothesis for why is that processing the largest sizes is the slowest, and once they’re in memory, making and writing the smallest variants doesn’t take additional time.
2. Upgrading libraries
Since I was going to start writing code, I wanted to work with newer code.
- upgraded Eleventy from v2 to v3
- translated everything to ESM01
- also updated a other packages as Claude deemed fit
I’m very grateful for AI tools to do all this grunt work I’d ordinarily never have wanted to wade through.
To my surprise, the newer versions seemed to speed things up a little bit, going from 25 mins → 23m mins.
3. Copy original images
Thanks to Claude, I forked eleventy-img and added a feature I’ve wanted for a long time: just copy over the original file if the settings match.
This cut down my builds from 23 mins → 12 mins (48% reduction).
Even though the speed won’t matter given improvement 4 (below), I’m keeping it because there’s another benefit: don’t re-process already-compressed files.
I’m careful about compressing my photos with mozjpeg at a desired quality (80 or 90). But with default eleventy-img, there’s no way to skip re-processing them. This not only takes time and likely reduces the quality by double-compressing, but it actually often increases the file size.
I’ve wanted this since at least May 2023: 11ty/eleventy-img#183 (my issue, closed). The author suggests passthrough copy, which doesn’t add width/height attributes. I’m not sure how I’d hookup passthrough copy (an eleventy feature) into eleventy-img (a separate plugin with its own world that must know about all an image’s sizes together). But even more importantly, I think the width and height attributes are vital for how I designed my HTML and CSS to reserve the correct amount of space on the page at first load and prevent layout shifts.
Here’s links to:
- the patch: GH:
mbforbes/eleventy-img@f751202 - my fork: GH:
mbforbes/eleventy-img - published: npm:
@mbforbes/[email protected]
4. Use build caching
After celebrating the above, but wishing it was even faster, I was pouring through all of Cloudflare page’s build options. Upgrading where I could, trying bun, etc. I happened to come across the Eleventy author’s post on using build caches.
Cloudflare lets you opt-in to copying the previous .cache/ directory between builds. Incredible.
I moved all my Image() calls to write to the .cache/ directory, then copy over to the generated site at the end of the build. I was already saving precomputed thumbhashes and image sizes there, too. It’s a teensy bit hairy because (a) I build image variants in several places (including a wholly separate preprocessing step), and (b) I also followed the author’s note of writing skipping this cache writing if serving instead of building.
This took builds from 12 mins → 1.5 mins.
What’s next?
Incremental builds seem slower now. They seem to write the whole site on each change. I’m guessing there’s some config that’s messed up somewhere, perhaps with the Eleventy v2 → v3 transition.
But overall, I’m really glad to have fewer files, not double-compress images, and not worry about build timeouts. And to get to see the site live just a couple minutes after I push.
Footnotes
ESM! At last. What a relief. ↩︎