All projects Project 6 of 10

PhotosByRH

A photography portfolio that keeps motion off the critical path, so the first photographs paint from the HTML.

No.06
Versionv1.0.0
RoleBuilt the galleries, the lightbox and the image pipeline that feeds both
Duration8 months
StatusIn production
StackNext.js 16, React 19, Tailwind CSS v4, Framer Motion, Vercel

My own photographs, in six galleries: wildlife, sports, pets, film, brands and event work. It is a portfolio, so the only thing that matters is that the picture arrives and nothing is in the way of it. Hero entrances are CSS animations, above-the-fold images are never hidden behind a scroll reveal, and the lightbox mounts only the photo you tapped on its first frame, with its neighbours prefetched once that one is on screen.

The palette is monochrome on purpose. On a page that is almost entirely photographs, a saturated interface colour competes with the only thing meant to carry colour, so the primary action is a white pill with a black label at 19.3:1. Chrome is a translucent material that fades in once content scrolls under it and honours the reduced-transparency and contrast settings.

One bug took some finding. The lightbox had been requesting a quality Next 16 does not allow, and Next answers an unlisted quality with a 400 rather than clamping it, so no full-size photo ever loaded and the blur placeholder simply stayed up. A later redesign rebuilt the lightbox around spring-driven gestures, and it was reverted to the earlier implementation.

Diagram of a grid of cells laid out in columns, with one cell outlined as the conflict and a status badge showing the last sync. COLUMN 1COLUMN 2 COLUMN 3COLUMN 4 SPAN 3 SYNCED 2D AGO
PhotosByRH Diagram
  1. 1
    No reveal on LCP Above-the-fold images paint from the HTML instead of waiting for hydration.
  2. 2
    Lightbox on click Only the photo you tapped mounts first; its neighbours follow once it is up.
  3. 3
    2560px masters A repeatable script downsamples every master, and the source JPEGs are gone.
  4. 4
    19.3:1 button A white pill with a black label, because a UI colour fights the photographs.

Overview

What I built

  • +Six galleries deployed, with a repeatable optimize script anyone can re-run after adding photos.
  • +Static image imports, so every gallery renders a blur placeholder at the right aspect ratio.
  • +A branded 404 page, cross-origin isolation headers, and text that clears WCAG AA.

What I rebuilt

  • ~Found why no full-size photo ever loaded: a quality of 85 against an allow-list of 75, which Next 16 answers with a 400.
  • ~Cut lightbox open latency by asking for 2x density and deferring the neighbours' prefetch.
  • ~Reverted the spring-driven redesign back to the pre-redesign lightbox implementation.

Known limitations

  • !A CSP nonce is not viable while every route is statically prerendered, so the policy ships without one.
  • !Any quality passed to next/image must be listed in images.qualities, and an unlisted one is a silent 400.

Decisions

  1. 1.
    CSS on the page, Framer in the lightbox I chose plain CSS transitions for page and grid motion, Framer Motion only inside the lightbox, Instead of animating every gallery with Framer Motion, Because CSS transitions run on the compositor, and hero entrances then start at first paint rather than after hydration..
  2. 2.
    Monochrome, not a brand hue I chose a white pill with a black label, 19.3:1, as the primary action, Instead of a saturated accent colour for the primary action, Because on a page that is almost entirely photographs, a UI colour competes with the only thing meant to carry colour..
  3. 3.
    Mount the lightbox late I chose mount only the photo that was clicked, on its first frame, Instead of mounting the whole lightbox with every neighbour up front, Because the neighbours are prefetched once the photo is on screen, so arrowing does not stall on a cold fetch..

Timeline

Version Date Description
v0.1.0 2025-12 First commits: base files, the image set, and a landing page that stopped breaking.
v0.2.0 2026-02 Sports, pets, wildlife and about sections, an image lightbox, and the galleries moved to WebP.
v0.3.0 2026-08 Image pipeline, blur placeholders, a 404 page, and a lightbox that actually loads.
v1.0.0 2026-08 System typography and a redesign, then the redesigned lightbox reverted to the earlier one.