Serve images from Cloudflare R2 instead of the repo - #71
Merged
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
cube-docs | 18462c0 | Commit Preview URL Branch Preview URL |
Aug 07 2026, 09:36 AM |
Images committed into public/img/ bloated PRs and repo history. They're now stored in the shared "websites-images" R2 bucket (prefix "cube-docs") and served through a hand-written Cloudflare Worker (worker/index.ts) added as `main` alongside the static-assets binding -- this site is a fully static Next.js export with no server runtime, so neither @cloudflare/next-on-pages nor @opennextjs/cloudflare applies. Authoring is unchanged: MDX content already referenced doc images by their final /img/... path (no relative-path convention existed here to begin with), so nothing about writing  needed to change. - source.config.ts disables fumadocs' remarkImage plugin, which otherwise needs the file on local disk at build time. - mdx-components.tsx's img: override renders the result as a plain, zoomable <img> (fumadocs-ui's ImageZoom wrapping a plain element, not next/image) -- no width or height required, so there's nothing to keep in sync when images change. ImageZoom's src/alt are passed explicitly (not just to the inner <img>), since its zoomed-in view reads the image from those props directly, not from `children`. - Dropped lib/image-dimensions.json entirely. All 58 content images have already been uploaded to the real R2 bucket and spot-checked byte-for-byte against the originals.
ianmuchyri
force-pushed
the
feat/r2-image-cdn
branch
from
August 6, 2026 16:01
84c7d3d to
b0c51c1
Compare
bucket.get() is an R2 binding call, not an HTTP subrequest. Workers run before Cloudflare's cache in the request pipeline, so a Response the Worker constructs and returns is never automatically written into the edge cache, no matter what Cache-Control header is set on it -- that only happens via explicit Cache API use, or a zone Cache Rule intercepting it. Neither was happening here, so despite s-maxage=31536000 being set, every request (every visitor, every edge location) was a live R2 read. Fixed by writing responses into the Workers Cache API (caches.default) after the first R2 read, keyed by the request's own URL unmodified (so it stays purgeable by the existing purge-by-URL call in the publish-image script on every upload). This also adds Range/206 support as a side effect: cache.match() automatically serves 206 Partial Content for a Range request against a cached 200 response. Bumped browser max-age from 300s to 3600s while leaving s-maxage at a year -- purge-on-publish already invalidates the edge instantly on every upload, so there's no freshness benefit to a short edge TTL. Same fix as absmach/website#178, applied here since this repo's worker/r2-proxy.ts uses the identical binding-without-caching pattern.
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.
Summary
public/img/bloated PRs and repo history. They're now stored in the sharedwebsites-imagesR2 bucket (prefixcube-docs) and served through a hand-written Cloudflare Worker (worker/index.ts) added asmainalongside the static-assets binding — this site is a fully static Next.js export with no server runtime.remarkImage(source.config.ts), which previously bundled referenced images into the webpack build at compile time.next/image'sImagecomponent (unoptimized: true) with explicitwidth/heightfrom a generatedlib/image-dimensions.jsonmanifest, replacing the prior plain-<img>rendering.components/BrandLogo.tsx's 2 fixed-size SVG logos were intentionally left out of this migration (out of scope, same boundary drawn on sibling repos) — they already usednext/imagecorrectly and aren't referenced from any MDX content.scripts/publish-image.mjs(maintainer-only, seescripts/README.md) to upload an image and purge the edge cache for it.public/img/(58 content images) from git. All 58 have been uploaded to the real R2 bucket and spot-checked byte-for-byte against the originals.Test plan
pnpm run lint,pnpm run types:check,pnpm run buildall pass locally with images genuinely absent from diskdata-nimgpresent on every doc image (genuine next/image output), correct dimensions, nosrcset(confirmsunoptimized)--remote) R2 bucket, spot-checked byte-identical against source🤖 Generated with Claude Code