perf: cut image-heavy build times ~38% and make image work observable - #397
Merged
Conversation
Three build-performance fixes, verified against a replica of a hosted builder environment (node:22-alpine, 4Gi, 2 vCPU) on a 210-page garden with 194MB of images (baseline 210s -> 131s): - Drop the full-resolution "auto" rendition from eleventy-img. fillPictureSourceSets only ever references the first two widths and the <img src> fallback serves the original file, so the full-size webp/jpeg re-encodes (the most expensive sharp work by far) were generated but never referenced by any markup. - Set UV_THREADPOOL_SIZE=16 for the production build. sharp's async operations run on the libuv threadpool, and at the default size of 4 multi-second image encodes monopolize the pool, serializing all other file I/O (passthrough copies, template reads) behind them. 16 sits on the measured plateau (16 ~= 32); 64 destabilized the build. - Cap eleventy-img queue concurrency at 2 and await all pending image jobs in an eleventy.after hook. Previously generation was fire-and- forget, so builds kept doing invisible sharp work after Eleventy reported completion - on hosted builders this ran into the job timeout and got killed with no attributable error. Also lowers --max-old-space-size from 4096 to 2048: peak heap measured far below this, and on 4Gi build containers a 4096MB heap ceiling leaves no headroom for sharp's native memory, turning heap growth into a silent container OOM kill instead of a readable error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GNhBMrXmzpXoswH4bCRWqT
oleeskild
added a commit
that referenced
this pull request
Aug 20, 2026
#397 lowered --max-old-space-size from 4096 to 2048 based on a 210-page garden. Large text-heavy gardens (~1100+ notes) exceed that: the build dies with a V8 "allocation failure" at ~2 GB heap while the 4Gi build container still has half its memory unused. 3072 gives those gardens room while still leaving ~1 GB on a 4Gi container for sharp's native allocations, which was the reason #397 lowered it in the first place. Co-authored-by: Ole Eskild Steensen <6201338+oleeskild@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three build-performance fixes, verified against a replica of a hosted builder environment (node:22-alpine, 4Gi, 2 vCPU) on a 210-page garden with 194MB of images (baseline 210s -> 131s):
Drop the full-resolution "auto" rendition from eleventy-img. fillPictureSourceSets only ever references the first two widths and the
fallback serves the original file, so the full-size webp/jpeg re-encodes (the most expensive sharp work by far) were generated but never referenced by any markup.
Set UV_THREADPOOL_SIZE=16 for the production build. sharp's async operations run on the libuv threadpool, and at the default size of 4 multi-second image encodes monopolize the pool, serializing all other file I/O (passthrough copies, template reads) behind them. 16 sits on the measured plateau (16 ~= 32); 64 destabilized the build.
Cap eleventy-img queue concurrency at 2 and await all pending image jobs in an eleventy.after hook. Previously generation was fire-and- forget, so builds kept doing invisible sharp work after Eleventy reported completion - on hosted builders this ran into the job timeout and got killed with no attributable error.
Also lowers --max-old-space-size from 4096 to 2048: peak heap measured far below this, and on 4Gi build containers a 4096MB heap ceiling leaves no headroom for sharp's native memory, turning heap growth into a silent container OOM kill instead of a readable error.