Skip to main content

· 2 min read

Caching Sharp-Processed Images in Static Builds

A documentation site I worked on processed thousands of images during every build. Sharp converted each one to webp, computed dimensions, and emitted multiple sizes. The work was the same across builds because the source images rarely changed, yet CI repeated it from scratch every time. Build times reflected that.

The Cache Boundary

The first instinct was to add a runtime cache. That helped local development but did nothing for CI, where every run starts cold. I needed the processed images themselves to survive between builds. So I moved the boundary: the preprocessed webp outputs and their metadata became part of the repo, committed under a generated images folder.

Now the build asked a simpler question: do I already have a processed version of this image? If yes, skip Sharp entirely and copy the cached output. If no, process it and add it to the cache.

Detecting Real Changes

Treating every modified file as a cache miss would defeat the point. I needed a stable hash that captured what actually mattered to the output: the source path and the file size. Two files with the same path and size produce the same processed result, so the same hash. Modified time is noise. Tracking it caused the cache to invalidate on git checkouts and other no-op changes.

A short hash from path plus size was enough. The rare collision risk was acceptable for a content site where every image is a known input.

The CI Side

CI sets a build environment variable. When the build sees it, the image processing step short-circuits and trusts the committed cache. New or modified images get processed in their own PR, where the cache update lands as part of the diff. Reviewers see the new webp file alongside the source.

The result was a build that went from minutes of image work to near-zero on most CI runs. The only time Sharp wakes up is when an image actually changed, which is exactly when you want it to.