# Stage 6 spike: `cacheComponents` — measured, and the answer is no Ran 2026-08-04 on branch `spike/cache-components` (deleted; nothing merged). ## The question the spike was meant to answer Next instruments `fetch`, `cookies()`, `headers()`, `params`, `searchParams` — **not `node:fs`**. Every loader in this editor is a raw `readFile`/`readdir`. Under `cacheComponents`, does uncached fs I/O outside a Suspense boundary get captured into the prerendered static shell at build time (→ a frozen admin UI), or is it correctly excluded? ## The answer: the build never gets far enough to ask ``` Route segment config "dynamic" is not compatible with `nextConfig.cacheComponents`. Please remove it. ``` **49 errors, one per file — exactly the 49 files in `editor/app/` that declare `export const dynamic = "force-dynamic"`.** `next build` fails outright. That is every page and every API route in the editor. `force-dynamic` is not incidental here; it *is* the app's rendering model. An admin UI over a live corpus with running job runners has nothing meaningful to prerender. ## What adoption would actually cost Not the "~48 × `await connection()`" the plan estimated. It is: 1. Delete `force-dynamic` from all 49 files — removing the one declaration that currently *guarantees* dynamic rendering; then 2. Prove, per route, that its uncached `node:fs` reads are excluded from the prerender — i.e. the original question, now asked with the safety net removed rather than in place. Getting it wrong ships a frozen admin page that looks fine in dev. 3. Plus the costs already known: `use cache` has no correct invalidation key here (writes happen in `common/` job runners that cannot import `next/cache`), and `Activity`-based navigation keeps routes mounted and re-runs effects, needing re-verification of the ~20 `router.refresh()`-on-mount components and much of the 407-spec suite. ## What it would buy: nothing we don't already have Sibling navigation is already instant — measured: with the destination prefetched, the click commits from the router cache with no pending state at all. The one thing `cacheComponents` would add is `unstable_instant`, which *guarantees* a loading fallback when the destination is NOT prefetched (see `editor/e2e/navigation.spec.ts` for why `loading.tsx` alone does not). That is a narrow gain for a migration that starts by deleting the app's rendering model. **Recommendation: do not adopt.** Revisit only if the editor grows nested layouts with their own data (there is exactly one layout today and zero nesting), which is where `unstable_instant`'s cross-layout validation earns its keep. Taken from those docs regardless, and already shipped: `loading.tsx` and `staleTimes`.