Repository navigation
Replies: 1 comment
|
We’re running into this too on a photography site using EmDash 1.2.0, Astro, Cloudflare Workers, and R2. Loading a gallery in a second browser appears to incur the transformation delay again, even on the same computer and network. Caching resized images for reuse across visitors feels like table stakes for a CMS, so I’m surprised this remains unresolved. The ETag/304 work helps returning browsers, but doesn’t address sharing generated variants with new visitors. This directly affects Core Web Vitals when the main image is the Largest Contentful Paint element. [Google’s LCP optimization guidance](https://web.dev/articles/optimize-lcp) recommends reducing resource load duration and using efficient caching. I’d expect the default CMS image pipeline to support that without each site implementing its own variant-storage system. The generated image URLs are also pretty rough: each srcset candidate contains an entire percent-encoded absolute CMS media URL inside Of the proposed approaches, versioned or content-hashed image URLs seem strongest: replacing an original changes its URL, allowing resized variants to be cached long-term without serving stale images. A compact variant URL would also be much cleaner. Is there a planned upstream solution for this? |
Uh oh!
There was an error while loading. Please reload this page.
I run three small client sites on EmDash 0.37 (Astro, Workers, D1, R2) and spent today tracking a 6.3s mobile LCP on an article page. The cause was the media cache policy, so I am sharing the numbers in case they help.
What I measured
Production, Cloudflare Free plan, cover image 1536x1024 WebP stored in R2. Five samples each. For the "fresh" column I requested widths the edge had never seen, so nothing could be served from cache.
BYPASS, always hits origin/_image(EmDash endpoint)/cdn-cgi/image/(Cloudflare edge)Where it comes from
src/astro/image-endpoint.ts:75setsMUTABLE_MEDIA_CACHE_CONTROLon transformed output.src/astro/routes/api/media/file/[...key].ts:58does the same for stored bytes, and the condition there reads inverted from the constant names: images get the mutable policy, everything that is not an image getsIMMUTABLE_IMAGE_CACHE.I understand the reason. "Replace original" (#2861) and "Replace image" (#2899) keep the same storage key and the same URL, so a long TTL would serve stale bytes after a replace.
What it costs
A transform URL is deterministic in its query (
href,w,h,f,q), so in practice each variant is already content addressed. Withmax-age=0no browser keeps it, and every cold edge location pays the transform again. The defaultsrcsetemits ten widths, so that is ten variants to warm per location, per image. On a low traffic site most visitors land on a cold one.I could not find prior discussion of this specific tradeoff.
must-revalidatehas come up for HTML (#2883, #2369) and cache purge in general (#2435, #2208, #2297), but not the media header itself. Apologies if I missed it.Three directions, and I am happy to send the PR for whichever you prefer
Make the policy configurable. Something like
emdash({ media: { cacheControl: 'immutable' } }), so a site that never uses in place replace can opt into a long TTL. Smallest change, keeps the current default.Purge on replace. No programmatic cache purge: content written outside the Worker (direct SQL, CI, external sync) leaves edge and object caches stale #2435 notes that purge by tag already exists internally but is not exposed. If the replace handlers called it, and optionally a user supplied callback for the CDN in front, the default could become a long TTL and the cost would fall on the rare replace instead of on every request. This is the direction I would like most as a user.
Content hash in the transform URL. If
/_imagecarried a short hash of the stored bytes, a replace would produce a different URL andimmutablewould be safe with no purge at all. Biggest change, but it removes the tradeoff rather than managing it.Per CONTRIBUTING I am asking before sending a feature PR.
Disclosure: the measurements above are my own, run against my production sites. I used Claude to help write this up.
All reactions