Skip to content

Lazy scene mounting

Demos that wait until you look

The catalog grid used to mount every demo on first paint — 289 live React subtrees on /components, each with its own timers and animations, running in rows nobody had reached yet. Now they mount when a card comes within one screen of the viewport, and the numbers below are the two builds measured side by side.

Measured from the completed build on 2026-09-12 06:33 UTC (build eoCv1yhT4VQe) by npm run build && npm run measure, and committed as docs/build-report.json. The numbers are not re-derived while the page renders, because a build cannot see its own output.

/prompts HTML weight

963.6 → 500.6 KB

48% less markup

Demos deferred in /prompts

116

0 mounted in the built HTML; the rest wait for the viewport

Work avoided on load

289

demo subtrees on /components that no longer mount off-screen

What changed

Catalog cards and prompt posters now hold their demo back until the card is within one screen of the viewport, then mount it for good. The placeholder reserves the exact height, so nothing reflows when a demo appears.

  1. 1. The page renders the card — title, kind, scores, stack, link — and a placeholder marked data-lazy-state="pending".
  2. 2. On the client, one IntersectionObserver per demo watches with a 320px root margin; the observer is created inside a frame so nothing is set from an effect body and layout is settled first.
  3. 3. When the card approaches, the demo mounts and the observer disconnects — it never re-observes, because a mounted demo is not going back.

The trade-off, stated rather than hidden

Mounting needs JavaScript. With JavaScript disabled the placeholders never fill in, so a no-JS visitor sees the catalog without animations. That is a real cost, accepted because the animation is decoration over information that stays in the HTML — and measured on the no-JavaScript page, where all 289 component titles are still present. The alternative, mounting everything eagerly, is what the previous build did, and it is the number on the left.