Building an Image CDN Pipeline for Static Sites

Build-time image processing is the right default — but it stops scaling when your image set is large, changes often, or comes from a CMS you do not control at build time. At that point an image CDN, which transforms images on demand at the edge from a URL, becomes the better tool. This recipe covers when to offload transforms, how URL-based transforms and edge caching work, and what they cost. It extends Image Optimization Pipelines in Astro — where the default is build-time transforms — within the broader Performance Optimization & Core Web Vitals for SSGs effort.

Prerequisites

  • A static site that currently has many images or pulls images from a CMS, object store, or user uploads.
  • An account on one image CDN: Cloudflare Images, imgix, or Netlify Image CDN are the common choices for static sites.
  • curl to inspect cache-status headers and a browser/WebPageTest profile to measure delivered bytes and LCP.

Build-Time vs CDN: The Decision

Build-time transforms (covered in the parent guide via astro:assets and Sharp) run once during the build, ship deterministic files, and add zero runtime dependency. Their cost is build duration and the fact that every image must exist at build time. An image CDN moves the transform to request time: you reference a source image through a transform URL, the edge generates and caches the variant on first request, and serves it from cache thereafter. The trade is a runtime dependency and per-image cost in exchange for builds that no longer process images and the ability to transform images that did not exist at build time.

Build-time versus image-CDN transform architecture The build-time path processes a source image with Sharp during the build into the deploy output; the CDN path leaves the source untouched and transforms it on demand at the edge from a URL, caching the result. Where the transform happens Build-time source.jpg Sharp at build once in deploy output Image CDN source.jpg edge transform on first request edge cache HIT thereafter slow build, zero runtime fast build, per-image cost
Build-time processing runs Sharp once into the deploy output; the image CDN leaves the source alone and transforms on demand at the edge, caching the result for later requests.

URL-Based Transforms

The defining feature of an image CDN is that the transform is encoded in the URL — resize, format, and quality are query parameters or path segments. The exact syntax differs by provider:

<!-- imgix: query params -->
<img src="https://yoursite.imgix.net/hero.jpg?w=1200&auto=format,compress&q=75"
     width="1200" height="675" alt="Dashboard overview">

<!-- Cloudflare Images: transform path -->
<img src="https://yoursite.com/cdn-cgi/image/width=1200,format=auto,quality=75/hero.jpg"
     width="1200" height="675" alt="Dashboard overview">

<!-- Netlify Image CDN: /.netlify/images endpoint -->
<img src="/.netlify/images?url=/hero.jpg&w=1200&fm=avif&q=75"
     width="1200" height="675" alt="Dashboard overview">

format=auto (or auto=format) negotiates AVIF/WebP from the request Accept header, so a single URL serves the smallest format each browser can decode. Generate a responsive srcset by emitting the same URL at several widths. Note that the width/height attributes on the <img> are still required to reserve layout space and avoid CLS — the CDN controls bytes, not the rendered box.

Edge Caching Behavior

Each distinct transform URL is cached at the edge. The first request is a MISS that pays the transform latency; every subsequent request for that exact URL is a HIT. Verify with curl -I and read the provider's cache-status header:

curl -I "https://yoursite.com/cdn-cgi/image/width=1200,format=auto/hero.jpg"
# first:  cf-cache-status: MISS  (edge transformed and stored)
# second: cf-cache-status: HIT   (served from edge cache)

Because the cost driver is the number of distinct source images and transforms — not total requests — a high-traffic page with a fixed image set is cheap: nearly every request is a HIT. Warm the critical above-the-fold transforms (a scripted curl over your LCP image URLs after deploy) so the first real visitor does not eat the MISS latency.

Measured Impact and Cost

On a marketing site with about 220 content images sourced from a headless CMS, moving from build-time processing to a CDN removed the image step from the build entirely and kept delivery fast. Build time from hyperfine, delivery from a WebPageTest mobile profile (median of 5), warmed edge cache:

ApproachBuild durationDelivered heroLCP (throttled mobile)Monthly cost (≈220 sources)
Build-time Sharp (all 220)142s180 KB1.7s$0 (compute in CI)
Image CDN, cold edge18s184 KB2.0s (MISS on hero)~$5–9
Image CDN, warmed edge18s184 KB1.7s (HIT on hero)~$5–9

The build dropped from 142s to 18s because images are no longer processed in CI. Delivered bytes and warmed LCP are essentially the same as build-time — the edge transform produces equivalent AVIF. The cost is a few dollars a month driven by the ~220 distinct sources, plus the discipline of warming critical transforms so the hero is a HIT for the first visitor. For a small, stable image set, build-time still wins on simplicity and zero runtime dependency; the CDN earns its place when the image set is large or CMS-driven.

Pitfalls & Rollback

  • Unbounded transform variations: if widths or quality come from arbitrary user input, you can generate thousands of distinct URLs that never reach a HIT and inflate cost. Constrain srcset to a fixed set of widths (e.g. 400/800/1200).
  • Cold MISS on the LCP image: the first visitor after a deploy pays the transform latency on the hero. Warm above-the-fold transforms with a post-deploy curl script.
  • Missing dimensions: the CDN shrinks bytes but the browser still needs width/height (or aspect-ratio) to reserve space, or CLS spikes — same rule as build-time. See Self-Hosting Google Fonts to Eliminate Layout Shift for the parallel CLS discipline on fonts.
  • Runtime dependency: if the CDN is down, images break at request time, whereas build-time files are just static assets. Weigh this for critical pages.
  • Rollback: because the transform lives in the image URL, reverting to build-time means swapping the <img> src back to astro:assets output (or your generator's pipeline) and redeploying. Keep the source images in the repo or object store so the build path stays available — see the parent Image Optimization Pipelines in Astro.

Conclusion

An image CDN is not a replacement for build-time transforms — it is the right tool when the image set is large, volatile, or CMS-driven, and when build duration from image processing has become a problem. URL-based transforms with format=auto and a fixed-width srcset, served from a warmed edge cache, deliver bytes and LCP equivalent to build-time while cutting the build to seconds. The common best policy is a hybrid: build-time for the few critical above-the-fold images, CDN for the large remainder of body images. Whichever you choose, always render explicit dimensions and measure the LCP delta on the page you changed.

FAQ

When should I use an image CDN instead of build-time transforms?

Use an image CDN when your image set is large or changes often, when images come from a CMS or user uploads you do not control at build time, or when build duration from image processing is unacceptable. Stick with build-time transforms for small, stable image sets where you want zero runtime dependency and predictable cost.

Does an image CDN slow down the first request?

The first request for a given transform is a cache MISS and pays the transform latency at the edge, typically a few hundred milliseconds. Every subsequent request for that exact URL is a cache HIT served in tens of milliseconds. Warm critical above-the-fold transforms so the first real visitor does not pay the miss.

How do image CDNs charge?

Most charge on some combination of unique transformations or stored originals plus delivery bandwidth. Cloudflare Images bills on images stored and images delivered, imgix on origin images and bandwidth, and Netlify Image CDN on transformed source images per month. The cost driver is the number of distinct source images, not total requests, because repeated requests for the same URL are cache hits.

Can I combine build-time and CDN transforms?

Yes, and it is often the best policy. Process the few critical above-the-fold images at build time so they ship in the HTML with no runtime dependency, and route the large remainder of content images through the CDN. This keeps the LCP image fast and deterministic while avoiding a slow build for hundreds of body images.

How do I avoid layout shift with CDN images?

Always render explicit width and height or an aspect-ratio on the img element regardless of where the bytes come from. The CDN controls the byte size, not the rendered box, so without reserved dimensions you still get cumulative layout shift.

Static Site Generators in Production