Performance

The gallery is optimized for a static-hosted, mobile-first photo browsing path. The current performance work was based on a PageSpeed mobile report from 2026-04-26 22:43 UTC, where the main bottlenecks were oversized first-screen thumbnails and JavaScript that was loaded before the user opened the viewer or special image formats.

Image Pipeline

The builder generates responsive thumbnail resources for every photo:

  • 360w WebP for narrow mobile columns.
  • 640w WebP for larger cards and higher-density screens.
  • 640w JPEG as the compatibility fallback.

The manifest keeps thumbnailUrl as the JPEG fallback and adds optional responsive fields:

interface PhotoManifestItem {
  thumbnailUrl: string
  thumbnailSrcSet?: string
  thumbnailWebpSrcSet?: string
}

thumbnailSrcSet currently points at the JPEG fallback and thumbnailWebpSrcSet points at both WebP variants. Existing consumers can keep using thumbnailUrl, while the gallery can render a <picture> element for modern browsers.

The masonry gallery treats the first six items as first-screen candidates:

  • First six thumbnails use eager loading and high fetch priority.
  • Later thumbnails remain lazy-loaded with normal priority.
  • Each gallery item uses <picture> with WebP first and JPEG fallback.
  • The Vite manifest injection plugin preloads the first two homepage thumbnails into index.html, so the browser can discover likely LCP images before React runs.

Manifest Loading

The build embeds a lightweight manifest into index.html for the gallery grid and emits the full manifest as a hashed JSON asset. The lightweight manifest keeps fields needed for sorting, filtering, thumbnails, localized descriptions, camera/lens display names, and first-screen rendering. Detail-heavy views, such as the manifest inspection page and photo detail data, fetch the full manifest through __FULL_MANIFEST_URL__ only when needed.

This keeps the initial viewport image budget low while preserving compatibility for browsers that cannot use WebP.

Static Photo Metadata

Production builds also generate static HTML shells for every photo detail route at photos/<photo-id>/index.html. These files reuse the SPA entrypoint but replace the title, description, canonical URL, OpenGraph tags, and Twitter Card tags with photo-specific metadata.

That metadata is generated from the manifest after photo-descriptions.json has been merged, so crawlers and link unfurlers can read localized manual descriptions before React loads. The feature adds HTML files to the static output, but it does not add extra JavaScript to the homepage path.

JavaScript Loading

The homepage path should avoid pulling viewer-only and conversion-heavy code:

  • Photo detail routes are no longer preloaded unconditionally from App.tsx.
  • The command palette is lazy-imported only when opened.
  • Live Photo and Motion Photo helpers dynamically import the image loader manager.
  • HEIC and TIFF converters are registered through dynamic imports, so ordinary JPEG and PNG viewer paths do not load heic-to or TIFF conversion code.
  • The PWA registration script is injected with script-defer.

Google Fonts are loaded with a non-blocking stylesheet pattern. The first paint uses system fonts and naturally swaps to Geist when the stylesheet is ready.

Regeneration

After changing thumbnail formats, manifest fields, or first-screen loading behavior, regenerate and verify the generated files:

pnpm run build:manifest -- --force-thumbnails --force-manifest
pnpm --filter web type-check
pnpm build
pnpm run bundle:budget

Use pnpm --filter web analyze when checking chunk boundaries. The home route should not eagerly include HEIC/TIFF converter chunks, command palette code, or the photo detail route.

Validation Checklist

Before publishing a performance change, check:

  • Mobile masonry thumbnails render through WebP with JPEG fallback.
  • Opening the photo viewer still works for ordinary images.
  • Live Photo and Motion Photo items still load their media.
  • Map markers and the manifest page still have usable thumbnails through thumbnailUrl.
  • index.html contains image preloads for the first two homepage thumbnails.
  • /photos/<photo-id>/index.html files contain photo-specific title, description, canonical URL, and social image tags.
  • registerSW.js is loaded with defer.
  • pnpm run bundle:budget passes after production build output exists.
Created At
Last Modified