DOCUMENT 07 — RENDERING STRATEGY
Each route is cached at the shortest interval its data tolerates and no longer. Stock is the only value read per request. The table below is generated from the same constant the render badge in every footer reads, so it cannot describe a plan the site is not running.
BUILD
- COMMIT
- 95eba8a
- BUILT
- 2026-07-31 02:53:16 UTC
- ROUTES
- 5 CACHED / 5 PER-REQUEST
- ISLANDS
- 7 CLIENT COMPONENTS
10 ENTRIES — GENERATED FROM lib/rendering.ts
| ROUTE | STRATEGY | REVALIDATE | REASONING |
|---|
| / | ISR | 3600 S | Editorial content changes hourly at most. The cheapest possible render that is still fresh. |
| /collections/[slug] | SSG | 3600 S | Four finite, high-traffic, rarely changing collections. Build-time cost is bounded; unknown slugs fall back to ISR. |
| /products/[slug] | ISR + ON DEMAND | 300 S | The spec sheet never changes and price rarely does, so it is cached. Stock is read separately and never cached. |
| /api/stock | EDGE | NEVER | Source of truth for the size grid. A cached stock response is a correctness bug, so this is never cached. |
| /search | SSR | NEVER | Output is a pure function of user input. Caching it is pointless and would serve one visitor another visitor results. |
| /journal/[slug] | SSG + ON DEMAND | AT BUILD | Long-form editorial, static once published. The publish webhook revalidates it so writers are not blocked on a deploy. |
| /engineering | SSG | AT BUILD | Generated from this manifest at build time, so it cannot describe a rendering plan the site is not running. |
| /cart | CLIENT | NEVER | Per-user state held in localStorage. No server involvement and no SEO value, so nothing to render or cache. |
| /studio | CLIENT | NEVER | A static signpost rather than an embedded Studio. Sanity 6 imports useEffectEvent in a way Next 15 will not resolve, so the editor runs standalone via `pnpm sanity dev` against the same schemas. |
| middleware.ts | EDGE | NEVER | Runs before the cache on every request, which is exactly why it stays this cheap: one header read, at most two cookie writes, and no I/O at all. |
THIS SESSION — MEASURED IN YOUR BROWSER
LCP — LARGEST PAINT
— S
NOT YET MEASURED — THRESHOLD 2.5 S
INP — INTERACTION
— MS
NOT YET MEASURED — THRESHOLD 200 MS
CLS — LAYOUT SHIFT
—
NOT YET MEASURED — THRESHOLD 0.1
TTFB — TIME TO FIRST BYTE
— MS
NOT YET MEASURED — THRESHOLD 800 MS
MEASURED LIVE WITH web-vitals ON THIS PAGE LOAD. NOT FIELD DATA — THIS PROJECT HAS NO RUM PIPELINE, AND A 75TH-PERCENTILE FIGURE OVER 28 DAYS WOULD BE INVENTED. BARS SHOW THE VALUE AGAINST GOOGLE’S GOOD THRESHOLD; SHORTER IS BETTER. INP APPEARS ONLY AFTER YOU INTERACT.
MEASURED FROM pnpm build
| ROUTE | FIRST LOAD | OVER BASELINE |
|---|
| framework baseline | 102 kB | — |
| / | 115 kB | 13 kB |
| /collections/[slug] | 134 kB | 32 kB |
| /products/[slug] | 134 kB | 32 kB |
| /engineering | 114 kB | 12 kB |
The original brief set a 90 kB budget for the product page. That is below the floor: Next 15 and React 19 ship 102 kB of shared framework code before a line of application code exists, which is why both numbers appear here. The figure worth defending is the one on the right — what this application adds — and the product page adds 32 kB, comfortably inside 90. Quoting only the total would have made an unreachable target look met or missed for the wrong reason.
CURRENCY LIVES IN A COOKIE
The obvious design has middleware set a request header that Server Components read via headers(). That call opts a page into dynamic rendering, which would have turned every static and ISR route here into SSR — destroying the thing this site exists to demonstrate. The middleware sets a cookie instead and a client island reads it.
STOCK IS THE ONLY UNCACHED READ
A product page is mostly a spec sheet, and a spec sheet does not change. Stock does, unpredictably. Caching them together means choosing between a stale size grid and an uncacheable page, so they are read separately: the page is ISR, and the size grid refreshes against an edge route that is never cached.
FILTERS LIVE IN THE URL
Facet state is in the query string rather than component state, so a filtered view is shareable, reloadable, and readable on the server. The cost is that reading searchParams renders that request dynamically — the unfiltered collection is prerendered, a filtered one is computed on demand.
GROQ EVERYWHERE, GRAPHQL ONCE
Search is the one route that speaks GraphQL. GROQ projections shape the response so the adapter receives exactly the keys its schema wants; GraphQL returns the document’s own field names and the mapping happens in TypeScript. Both paths exist here on purpose, so the difference is visible rather than asserted.