Skip to content

perf: serve images at the size the page actually shows - #43

Merged
yorkerhodes3 merged 2 commits into
mainfrom
agents/refresh-from-github-repo
Aug 25, 2026
Merged

perf: serve images at the size the page actually shows#43
yorkerhodes3 merged 2 commits into
mainfrom
agents/refresh-from-github-repo

Conversation

@yorkerhodes3

Copy link
Copy Markdown
Contributor

What

Images were committed at their original camera resolution and served unchanged. output: "export" requires images: { unoptimized: true } — GitHub Pages has no image optimization server — so next/image does no resizing, emits no srcset, and does no format negotiation. The committed file is exactly what every visitor downloads.

Nothing in the intake resized anything, so headshots went up straight from a phone. team/alex.jpg was 2644×2644 / 1,096 KB to fill a 48px circle, and /team weighed 8.5 MB.

95 images, 16.01 MB → 4.57 MB (−71%).

Route Before After Change
/team 8.5 MB 2.1 MB −76%
/demos 4.6 MB 2.4 MB −48%
/media 3.5 MB 2.4 MB −32%
/ 2.3 MB 1.3 MB −46%
/team/<person> 2.6 MB 1.1 MB −60%

This matters more here than on most hosts: GitHub Pages serves everything with Cache-Control: max-age=600 and will not allow custom headers, so a returning visitor re-pays the full weight every ten minutes. Sending fewer bytes is the only available lever.

Analysis and method: docs/PERFORMANCE.md.

How

  • scripts/optimize-images.mjs resizes in place against a per-directory budget (avatars 384px, logos 256px, posters 600px, summit 1200px, backgrounds/heroes 1600px), with per-file overrides.
  • npm run check:images runs in CI, so the next oversized headshot fails the pull request instead of shipping.
  • Budgets documented in CONTRIBUTING.md and public/team/README.md.
  • sharp promoted from a transitive next dependency to an explicit devDependency.

Reviewer notes

Formats and filenames are unchanged on purpose. Converting to WebP would have saved another ~10%, but renaming an asset means editing every reference in src/content, and a missed one fails silentlyTeamAvatar falls back to initials rather than showing a broken image. Nearly all of the win is the resize.

A silent corruption bug this nearly shipped. sharp's png({ effort }) and png({ palette }) put libvips into its palette-quantizing path, which drops the alpha channel on some images. It hit four logos. coinbase.png is mostly transparent, so it would have rendered as a solid dark rectangle on the white logo tile — while passing the build, the byte budget, and every automated check. The encoder now uses compressionLevel only and verifies transparency survived. Worth knowing before anyone tunes the encoder for a few more KB.

Lossy and irreversible. Every image was re-encoded in a single pass from the originals, so there is no generational loss. Full-resolution originals remain in git history at 32e1ab9f.

Two commits, split per CONTRIBUTING §4 — source first, then the generated static-site/ snapshot, so the real diff stays reviewable.

The __next.* prefetch payloads are deliberately untouched in the snapshot commit. Next writes those with a platform-dependent path shape, so regenerating on Windows would delete 140 Linux-shaped files and add 75 Windows-shaped ones with no content change. CI excludes them from its drift check for the same reason (UPD-004).

Verification

  • CI already passed on this exact commit on Linux: run 32882271054 — lint, typecheck, content invariants, image budgets, build, and snapshot drift all green.
  • All 95 images compared against their originals: 0 lost transparency, 0 changed aspect ratios, 0 decode failures.
  • The CI gate was negative-tested — re-adding the 2644px headshot fails with the command that fixes it.
  • The optimizer is idempotent; a second run is a confirmed no-op, so it never re-compresses a compliant file.
  • Spot-checked /team, a profile page, /demos, and the partner logo band in both dark and light themes.

Also: EXIF is stripped after orientation is applied, which removes any GPS coordinates the cameras recorded on the published headshots.

Not included

Responsive srcset, lower book-render scale, lazy book pages, and removing /font-lab are tracked as UPD-015 and UPD-016. The font lab is recent in-flight work, so removing it is its author's call.

Yorke Rhodes III and others added 2 commits August 25, 2026 14:08
`output: "export"` forces `images: { unoptimized: true }`, so next/image does
no resizing and the committed file is exactly what every visitor downloads.
Nothing in the intake resized anything, so headshots went up straight from a
phone: team/alex.jpg was 2644x2644 and 1,096 KB to fill a 48px circle, and
/team weighed 8.5 MB.

Resize every committed image to its display size in one pass from the
originals - 95 images, 16.01 MB to 4.57 MB, taking /team to 2.1 MB. Formats
and filenames are unchanged on purpose: renaming an asset means editing every
reference in src/content, and a missed one fails silently to initials rather
than to a visible broken image. Nearly all of the win is the resize anyway.

Add scripts/optimize-images.mjs to do it, and check:images to CI so the next
oversized headshot fails the pull request instead of shipping. The script only
touches files that break their budget, so it never re-compresses a compliant
file a second generation, and it applies EXIF orientation before stripping
metadata - which also drops any GPS coordinates the camera recorded.

Note for anyone editing the encoder: png({ effort }) and png({ palette }) put
libvips into its palette path, which silently drops the alpha channel. That
turned coinbase.png opaque, which would have rendered as a dark rectangle on
the white logo tile while passing every automated check. It now encodes with
compressionLevel only and verifies transparency survived.

Full-resolution originals remain in git history at 32e1ab9.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Picks up the resized images from the previous commit. No HTML, JS or CSS
changed - the filenames are the same, only the bytes behind them.

The __next.* prefetch payloads are left as they are. Next writes those with a
platform-dependent path shape, so regenerating on Windows would delete the
Linux-shaped files and add Windows-shaped ones without changing any content.
CI already excludes them from its drift check for the same reason (UPD-004).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@yorkerhodes3
yorkerhodes3 merged commit fa5775e into main Aug 25, 2026
2 checks passed
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