Skip to content

perf: serve MDX screenshots as WebP through Next's image optimizer - #2010

Draft
marcleblanc2 wants to merge 3 commits into
mainfrom
marc/perf/webp-images
Draft

marcleblanc2 wants to merge 3 commits into
mainfrom
marc/perf/webp-images

Conversation

@marcleblanc2

Copy link
Copy Markdown
Contributor

Stacked on #1975. Diff against that branch is the only thing to review here.

Problem

MDX screenshots are uncompressed PNGs on GCS (sourcegraphstatic.com, storage.googleapis.com/sourcegraph-assets), served with max-age=3600 and no format negotiation. /batch-changes/delete-a-batch-change ships 1.2 MB of PNG for four images.

Those images sit behind no CDN today. Response headers show the request path is Cloudflare → Google front end → Vercel edge for HTML and static assets, but sourcegraphstatic.com is a separate zone on Google Cloud DNS pointing straight at a GCS load balancer: no Cloudflare, no Cloud CDN, no Vercel. Vercel's image optimizer has never been used on this project (zero transformations in the billing export for August and September).

Fix

ZoomableImage points allow-listed remote PNG/JPEGs at /_next/image: WebP, capped at 1920 px wide, cached at Vercel's edge for a week (minimumCacheTTL). The allow-list lives in docs.config.js so next.config.js (images.remotePatterns) and ZoomableImage share it. SVG, GIF and other hosts pass through unchanged. The zoom modal keeps the original full-resolution file.

Measured with next start, the four images on the page above:

image PNG WebP via /_next/image
batch_changes_icon_in_menu (1320 px) 24 KB 6 KB
closing_batch_change_close_icon (2250 → 1920 px) 439 KB 80 KB
closing_batch_change_close_changesets (2300 → 1920 px) 275 KB 49 KB
closing_batch_change_deleting (2452 → 1920 px) 476 KB 78 KB
total 1214 KB 214 KB

Cost

From the Vercel billing API (/v1/billing/charges) for the Sourcegraph team, which is on Pro:

  • Image Optimization usage today: 0 transformations, 0 cache reads, 4 cache writes team-wide (all on docs-legacy-versions), $0.
  • Estimated after this change: ~214 distinct image URLs in docs/, one variant each (w=1920&q=75, WebP), refreshed weekly ≈ 1–2K transformations and ~10–20K cache-write units a month. Pro's included allotment covers that; past it, transformations are $0.05 per 1K.
  • Fast Data Transfer: the optimized bytes move from Google's bill to Vercel's. Pro includes 1 TB/month under Flat Rate CDN; the team used 224 GB last period and sourcegraph-docs runs 40–60 GB/month. Even at 500K image-page views a month (~100 GB) this stays inside the included capacity, at $0.

Trade-offs

  • A screenshot re-uploaded under the same URL can take up to a week to refresh at the edge. Drop minimumCacheTTL to fall back to Next's 4 h default if that matters.
  • w=1920 is the largest of Next's default deviceSizes that still covers the content column on a 2x display; wider screenshots are downscaled. The zoom modal still loads the original.
  • Putting sourcegraphstatic.com behind Cloudflare would be a further improvement (free egress, closer edge) but is an infra change to a shared bucket's DNS, tracked separately.

Verification

tsc, eslint, cspell clean. On next start (earlier revision of this branch): every sourcegraphstatic.com PNG on the page renders through /_next/image as image/webp with Cache-Control: public, max-age=604800; a PNG from a bucket outside the allow-list returns 400. Page renders unchanged. Will re-check on the Vercel preview once it deploys.

marcleblanc2 and others added 3 commits September 17, 2026 21:00
ZoomableImage renders every doc image eagerly, so a long page downloads
every screenshot on it before first paint. loading="lazy" defers the
ones below the fold until the reader scrolls near them.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b274-3173-77ed-8f71-82301b157a16
Co-authored-by: Amp <amp@ampcode.com>
The docs embed ~360 PNG screenshots from GCS (sourcegraphstatic.com and
storage.googleapis.com/sourcegraph-assets), uncompressed and with a
one-hour cache. Route allow-listed PNG/JPEG sources through /_next/image,
which converts to WebP, caps width at 1920px and caches at Vercel's edge
for a week. The zoom modal keeps the original full-resolution file.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0ac9d-704f-73fe-8211-1b3e5840969f
Co-authored-by: Amp <amp@ampcode.com>
@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
sourcegraph-docs Ready Ready Preview Sep 18, 2026 3:36am UTC

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Spell check found 4 issue(s) in this PR

Only findings on lines added by this PR, and in its title and description, are shown.

Pull request description

  • line 5, column 48
    • sourcegraphstatic
  • line 7, column 158
    • sourcegraphstatic
  • line 35, column 12
    • sourcegraphstatic
  • line 39, column 92
    • sourcegraphstatic

Please correct the spelling, or add words which are correct to cspell-allow-list.txt.

Run npx cspell@10 --no-progress --dot '**/*' locally to check the full repository.

@marcleblanc2
marcleblanc2 force-pushed the marc/perf/lazy-load-images branch from 3369fd5 to f6911a6 Compare September 18, 2026 04:58
Base automatically changed from marc/perf/lazy-load-images to main September 18, 2026 04:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant