[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-vercel-next-cache-components-adoption":3,"mdc-gjfd27-key":50,"related-org-vercel-next-cache-components-adoption":2832,"related-repo-vercel-next-cache-components-adoption":2996},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":26,"repoUrl":27,"updatedAt":28,"license":29,"forks":30,"topics":31,"repo":45,"sourceUrl":48,"mdContent":49},"next-cache-components-adoption","enable and migrate to Next.js Cache Components","Turn on Cache Components in a Next.js app and resolve the blocking routes it surfaces. Use when the user wants to enable, adopt, or migrate to Cache Components, flip the `cacheComponents` flag, work through a flood of blocking-prerender \u002F instant validation errors, run the `cache-components-instant-false` codemod, or decide between opting routes out with `export const instant = false` and fixing them in place.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"vercel","Vercel","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fvercel.png",[12,14,17,20,23],{"name":9,"slug":8,"type":13},"tag",{"name":15,"slug":16,"type":13},"Next.js","next-js",{"name":18,"slug":19,"type":13},"Migration","migration",{"name":21,"slug":22,"type":13},"Caching","caching",{"name":24,"slug":25,"type":13},"Frontend","frontend",141208,"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js","2026-07-24T05:38:30.118542",null,31587,[32,33,34,35,36,37,38,39,40,41,42,43,44,8],"blog","browser","compiler","components","hybrid","nextjs","node","react","server-rendering","ssg","static","static-site-generator","universal",{"repoUrl":27,"stars":26,"forks":30,"topics":46,"description":47},[32,33,34,35,36,37,38,39,40,41,42,43,44,8],"The React Framework","https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002FHEAD\u002Fskills\u002Fnext-cache-components-adoption","---\nname: next-cache-components-adoption\ndescription: >\n  Turn on Cache Components in a Next.js app and resolve the blocking routes it\n  surfaces. Use when the user wants to enable, adopt, or migrate to Cache\n  Components, flip the `cacheComponents` flag, work through a flood of\n  blocking-prerender \u002F instant validation errors, run the\n  `cache-components-instant-false` codemod, or decide between opting routes out\n  with `export const instant = false` and fixing them in place.\n---\n\n# next-cache-components-adoption\n\nEnable Cache Components on an app and walk it to a passing build. This skill sequences the work; per-error recipes live in the dev overlay fix cards and the build's terminal output. The [migrating to Cache Components guide](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fmigrating-to-cache-components) is the canonical reference for the concepts and per-API recipes this skill applies — consult it whenever the skill steps reference a pattern (`\"use cache\"`, `cacheLife`, `\u003CSuspense>` placement, etc.) and you want the full explanation.\n\n## requires\n\n- **App Router project.** Cache Components is an App Router feature; `cacheComponents: true` does nothing for `pages\u002F` routes. If the project has a `pages\u002F` or `src\u002Fpages\u002F` tree but no `app\u002F` or `src\u002Fapp\u002F` tree, stop and tell the user — Pages → App migration is its own project, not part of this skill. A hybrid app (both `pages\u002F` and `app\u002F`) is fine: the flag affects the `app\u002F` routes; `pages\u002F` routes are unaffected and don't need opt-outs.\n\n- **A runnable app.** The whole loop verifies against `next dev` and a browser, so the app has to boot. If it reads a database or required env at import (e.g. an `env.ts` that throws on a missing `DATABASE_URL`), confirm it actually starts — with the real environment, or local data you stand up — before step 1. Adoption can't be verified against an app that won't run.\n\n- **Next.js 16.3 or later.** That release is where the pieces this skill relies on land: top-level `cacheComponents`, `export const instant`, the dev-overlay instant-navigation validation warnings, and the `cache-components-instant-false` codemod. If `next --version` reports below 16.3, upgrade first:\n  - `npx @next\u002Fcodemod@latest upgrade latest` to apply the version-to-version codemods.\n  - Read the relevant [version upgrade guide](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fupgrading) (e.g. [Version 16](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fupgrading\u002Fversion-16)) for what the codemod doesn't cover.\n\n- **No incompatible config keys.** `cacheComponents: true` errors on any file that still exports `dynamic`, `revalidate`, or `fetchCache`. **Translate, don't delete.** Each export encodes behavior the route needs to keep doing; migrate each one to its Cache Components equivalent via the [migration guide's per-key sections](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fmigrating-to-cache-components#enable-cache-components). The exception is `dynamic = 'force-dynamic'`: under Cache Components every route is already dynamic by default, so the migration guide removes it outright rather than translating it — don't overthink a batch of identical `force-dynamic` deletions. `revalidate` and `fetchCache` still need real translation. If a value can't be cleanly translated yet, leave a `\u002F\u002F TODO: Cache Components adoption — restore revalidate = 3600` comment so the loop picks it up. The `cache-components-instant-false` codemod does not touch these.\n\n- **`experimental.dynamicIO` is fatal.** It was renamed to top-level `cacheComponents` and the old key now aborts before any build can run — remove it (or replace with `cacheComponents: true`) first. `experimental.useCache` is still accepted as a deprecated alias; redundant once `cacheComponents: true` is set, so remove it for clarity.\n\n### notes\n\n- **No passing baseline before the flag.** If the app already uses `\"use cache\"`, the pre-flag build errors with `please enable the feature flag cacheComponents`. Enabling the flag is the first thing you do (in Incremental, before the codemod; in Direct, before fixing routes) — not a thing to do _after_ getting a passing build. Note this in your starting summary so it doesn't read as a regression.\n\n- **Offline docs.** Guide links have offline copies under `node_modules\u002Fnext\u002Fdist\u002Fdocs\u002F` (bundled since Next.js 16.2), with the directory layout numbered for ordering (e.g. `node_modules\u002Fnext\u002Fdist\u002Fdocs\u002F01-app\u002F02-guides\u002Fmigrating-to-cache-components.md`). If you can't predict the numbered prefix, `find node_modules\u002Fnext\u002Fdist\u002Fdocs -name '\u003Cslug>.md'` resolves it. The `\u002Fdocs\u002Fmessages\u002F*` error pages are not bundled.\n\n- **Older versions without bundled docs.** Suggest `npx @next\u002Fcodemod@latest agents-md` to the user before starting: it downloads a version-matched copy to `.next-docs\u002F` and writes an index into `AGENTS.md` \u002F `CLAUDE.md`. It touches files in their repo, so ask first and run it only if they want it.\n\n## the shape of the work\n\nThere's one loop: walk the route tree top-down, one feature at a time, adopting each route against `next dev` + a browser. The build is a final check for each feature, not the working surface.\n\nThe choice in step 1 is whether to opt every route out of validation first or fix routes as you go. Either way the loop is the same:\n\n- **With a quiet pre-step (Incremental).** Run the codemod to opt every page and layout out of validation. Once you've also fixed what the codemod can't (sync-IO calls, leftover `revalidate`\u002F`dynamic`\u002F`fetchCache` exports), the build passes; you ship that as its own PR and then start the loop — removing one opt-out at a time and adopting that route. This splits the work into small, reviewable PRs.\n- **Without (Direct).** Enable `cacheComponents` and start the loop on whatever the build flags first. Same loop, but every fix sits on one branch until adoption is complete.\n\nIn both, the per-route success bar is the same: **dev loop reports no errors AND `next build` passes**. Check in with the user after every feature, and suggest a commit but never make one without their confirmation. Expect to spend most of the time in the loop, not in the pre-step.\n\n## background\n\n`cacheComponents: true` requires every route to be prerenderable. A route that reads request-time data outside `\u003CSuspense>` is \"blocking\" and fails the build. `export const instant = false` marks a route as allowed to block, which clears it in both dev and build; on a layout it covers the whole subtree during the build, but client navigations still validate each descendant segment on its own. Reads wrapped in a [`\"use cache\"`](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fapi-reference\u002Fdirectives\u002Fuse-cache) function count as cache boundaries, not blocking reads.\n\nThree classes of blocker come up, usually in this order:\n\n1. **Request-time reads** (`cookies()`, `headers()`, `await params`, `await searchParams`). All four block when awaited at the top of a page or layout. `params` and `searchParams` often get missed because they're not framed as \"request data\" the way cookies and headers are. The fix is to push the read into a `\u003CSuspense>`-wrapped child — and for `params`\u002F`searchParams`, forward the promise into the child and await it there; don't `await` at the page top.\n2. **Sync-IO at module\u002Frender time** (`new Date()`, `Date.now()`, `Math.random()`, `crypto.randomUUID()`). These fail the build even with `instant = false` — the opt-out doesn't suppress them. If they're in a shared layout, they block every route under it. The codemod can't fix them; you have to translate each one by hand before the build can pass (see the [incremental pre-step](#incremental)). Grep the whole repo for these calls before running anything else.\n3. **`\"use cache\"` files that read request data.** A file with a top-level `\"use cache\"` directive can't export `instant`; combining the two errors with `Only async functions are allowed to be exported in a \"use cache\" file.`, which means the directive was wrong for that route. Remove it before running the codemod.\n\n## working surfaces\n\n### finding blocking routes\n\nPrefer `next dev` over `next build` while you work.\n\n- **`next dev`** — the working surface. Visit a route; its blocking errors surface in the dev overlay with full stack traces and fix cards linking the per-error docs. Work one route at a time — errors don't accumulate in one place. The route itself still returns HTTP 200, so read the overlay (or `.next-dev.log`), not status codes. A cleared overlay is one half of calling a route clean — the other half is browser verification (see [step 2](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time)) and a passing build for that route.\n- **`next build`** — detection only. The build is `next dev`'s authoritative check, not its replacement. Use it as the last gate on each feature in the loop (a passing build is part of the per-route success bar) and as the final verification across the whole app. In Incremental, the build also confirms the pre-step (codemod opted every route out, no shared layout still has a sync-IO blocker) before you ship that PR. Don't reach for the build instead of the dev loop while you're working a route — a passing compile doesn't tell you what ended up in the static shell and what streamed. By default the build stops at the first blocking route, so it's also poor for sizing the work. Two flags help when iterating: `--debug-build-paths` builds only the routes you name (comma-separated glob patterns of file paths relative to the project root, e.g. `--debug-build-paths=\"app\u002Fadmin\u002F**\u002Fpage.tsx\"` — not URL paths; `--debug-build-paths=\"app\u002F(marketing)\u002Fabout\u002Fpage.tsx\"` — not `\u002Fabout`; `--debug-build-paths=\"app\u002Fadmin\"` matches nothing and silently builds zero routes), and `--debug-prerender` disables the early exit so the build continues past the first prerender failure, reports every blocking route, and prints a fuller stack trace that names the originating file and line.\n\nEvery blocking error has a docs page — open it. Both the dev overlay and the build terminal print a `https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fmessages\u002F\u003Cslug>` link with each error. That page is the canonical recipe for the fix; the inline message is a summary. Fetch the link for every distinct error you encounter, even if you think you know the pattern — the recipes evolve, and the same error class can have different correct fixes depending on what the route reads. Don't improvise from the inline message alone. (`\u002Fdocs\u002Fmessages\u002F*` pages aren't bundled offline; if you have no network, fall back to the per-API guides under `node_modules\u002Fnext\u002Fdist\u002Fdocs\u002F` and note the limitation when you report back.)\n\n### verifying each fix at runtime\n\nA passing build or a cleared overlay isn't proof the route actually behaves — Cache Components is a runtime concern (a static shell with streamed data). Verify after every fix, not only at the end.\n\nIn preference order:\n\n1. **[`next-dev-loop`](https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-dev-loop) — strongly preferred.** Cross-checks `\u002F_next\u002Fmcp` against the live browser via `agent-browser` and surfaces both compile and runtime issues in one pass. The diagnostics (React tree, suspense boundaries, console + network) are richer than poking at `next dev` by hand.\n\n   Install it before starting the loop. Don't wait until you hit something `next dev` alone can't explain. Run:\n\n   ```bash\n   npx skills add https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-dev-loop\n   ```\n\n   The skill states its required `agent-browser` version and walks you through it.\n\n   **Requires Turbopack.** If `package.json`'s `dev` script passes `--webpack`, flag it to the user and ask whether there's a reason to stay on webpack. If not, switch to Turbopack (the Next.js 16.3+ default). If they want to keep webpack, skip this install and use the [build-only loop](#the-loop-build-only-fallback) instead.\n\n   You don't need permission to install `next-dev-loop` itself. It's a tool, like installing a dev dependency. If a user is present, briefly tell them you're installing it for verification. In a non-interactive run (CI, dashboard, sandbox), install it without asking — \"can't prompt the user\" is not a reason to skip. The only legitimate skip is a real technical blocker: no network, no npm, read-only filesystem, a stated no-new-deps policy, or a webpack-only dev script. If you skip, name the specific blocker in your final report.\n\n2. **A browser you can drive yourself.** Playwright, `agent-browser` directly, any browser-automation tool. Use only when `next-dev-loop` is genuinely blocked. You'll miss the framework-side checks (`\u002F_next\u002Fmcp`), so DOM assertions alone don't catch every regression — be more cautious about what you call \"verified.\"\n\n3. **Build-only.** If you can't run a dev server at all, the build is your only signal. `○ (Static)` routes with no `\u003CSuspense>` are fully verified by the build (nothing streamed to test). `◐ (Partial Prerender)` routes are only shell-verified — flag them when you report back.\n\n4. **No tooling at all.** Ask the user to run the dev server (or build) and report what they see, or hand off the milestone you've reached.\n\n## step 1: choose a strategy\n\nAsk the user, in terms of the PRs they want, not the size of the job. Never use the internal labels (Incremental, Direct) when talking to the user — those are your own scaffolding. Ask in terms of PRs and features, e.g.: _\"Do you want me to first open a PR that turns on Cache Components and opts every route out of validation, then handle the actual route adoptions feature-by-feature in follow-up PRs? Or do everything on one branch?\"_ Even on a tiny app, the incremental path still has value (review-sized PR, revertible, the `\u002F\u002F TODO: Cache Components adoption` markers double as your work queue for next session). Don't pick on their behalf.\n\nIf there's no user to ask, default to **Incremental** and document the choice.\n\n- **Incremental** — quiet pre-step + the loop. Run the codemod to opt every page and layout out of validation, get the build passing, stop and check in with the user (see [end of the pre-step](#end-of-the-pre-step-check-in)), then enter [step 2's loop](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time) and ship each feature as a follow-up PR.\n- **Direct** — skip the pre-step. Enable `cacheComponents` and go straight to [step 2's loop](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time); the build's blocking routes are the work queue.\n\n### incremental\n\nBefore invoking the codemod, fix the two classes of blocker it can't.\n\n1. **Sync-IO at module\u002Frender time.** Grep the whole repo for `new Date()`, `Date.now()`, `Math.random()`, and `crypto.randomUUID()` (not only `app\u002F**\u002Flayout.{js,jsx,ts,tsx}` — the read might live in any component imported by a layout). Unblock each match with the `await connection()` + `\u003CSuspense>` fix from its `blocking-prerender-*` error card: it defers the value to request time, exactly as it behaved before the migration, so it needs no product decision. Add this exact comment on the line above the `await connection()`:\n\n   ```tsx\n   \u002F\u002F TODO: Cache Components adoption. Added to unblock the build: remove this connection() to re-trigger the error and review the fix options.\n   ```\n\n   It shares the `TODO: Cache Components adoption` prefix with the comments the codemod writes, so the check-in grep finds both. Removing the `await connection()` makes the error fire again with its fix cards — the same motion as removing an opt-out in the loop.\n\n2. **Incompatible segment configs.** Grep for `^export const (revalidate|dynamic|fetchCache)` across `app\u002F` and translate per the `requires` note above. The codemod does not touch them; leaving them in place fails the build after the codemod.\n\nThe codemod refuses to run on a dirty working tree. Commit or stash unrelated work first, or pass `--force` to let its edits land alongside your WIP. Common false positive: if you recently upgraded Next.js, `package.json` and the lockfile will already be dirty — commit those first.\n\nUse the `@canary` channel, not `@latest`. The `cache-components-instant-false` transform isn't in the stable `@next\u002Fcodemod` release; `@next\u002Fcodemod@latest` errors with `Invalid transform choice`.\n\n```bash\nnpx @next\u002Fcodemod@canary cache-components-instant-false .\u002Fapp\n```\n\nInserts `export const instant = false` (with a `\u002F\u002F TODO: Cache Components adoption` comment) into every `app\u002F**\u002F{page,layout,default}` file, skipping files that already declare `instant` and any module marked `\"use client\"` or `\"use server\"`. Then set `cacheComponents: true`. The TODO comments are the work queue for the loop.\n\nIf the codemod isn't available (older `@next\u002Fcodemod`, sandboxed environment, offline run), reproduce it by hand: for every `app\u002F**\u002F{page,layout,default}.{js,jsx,ts,tsx}` that isn't `\"use client\"` or `\"use server\"` and doesn't already declare `instant`, insert this after the imports:\n\n```ts\n\u002F\u002F TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.\n\u002F\u002F See: https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fmigrating-to-cache-components\nexport const instant = false\n```\n\nThe codemod opts every segment out, not only the root, on purpose. Resolution is top-down, first-explicit-config-wins: the highest `instant = false` decides the whole subtree. With an opt-out on every segment, removing one segment's opt-out validates only that segment; descendants keep their own opt-outs and stay passing. If only the root were opted out, removing it would re-arm validation for the entire app at once.\n\nBecause the highest opt-out wins, remove them top-down (root layout first, then descend). Removing a leaf's opt-out does nothing while an ancestor still holds one.\n\nConfirm the pre-step with `next build`. The build is the proof, not the codemod run — a shared layout that calls `new Date()` \u002F `Math.random()` directly still fails regardless of the opt-out (see [background](#background)).\n\nAfter the build passes, confirm the root layout got an opt-out (`grep -n \"export const instant\" app\u002Flayout.*`). The root layout renders every route, including framework routes like `\u002F_not-found`, so if it was missed, add `export const instant = false` to it by hand.\n\nSynthetic routes like `\u002F_not-found` have no user file — when they block, fix the root layout's opt-out, not the synthetic route. Client Components (`\"use client\"`) get no opt-out (it's a build error — `E1344` — to export `instant` from them), but they are not a rare blocker. The high-frequency case is a client component in the root layout's nav or header calling `usePathname()`\u002F`useSearchParams()`: it blocks _every_ dynamic route with `blocking-prerender-client-hook`, and static routes pass (the pathname is known at prerender), which masks it until you reach a dynamic segment. It's not an ancestor-data fix — follow the [error's docs page](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fmessages\u002Fblocking-prerender-client-hook) for the `\u003CSuspense>` recipe. Only when a client route blocks on _server_ data do you fix that data in its ancestor.\n\n### end of the pre-step: check in\n\nIncremental only. Stop here before starting step 2 — the pre-step is the shippable PR. Talk to the user in their language; don't say \"Incremental\" or other internal labels; talk about adoption, PRs, and what the app does now. Tell them:\n\n- What you did: turned on Cache Components, ran the codemod that opts every page and layout out of the new validation (or did it by hand), fixed any blockers the codemod can't (list them), confirmed the build passes.\n- What changed: every page and layout in `app\u002F` now exports `instant = false` with a `\u002F\u002F TODO: Cache Components adoption` comment, except client components and any that already had an `instant` export.\n- What to sanity-check: the diff is mostly mechanical (new exports + comments). The build passes. Routes still behave exactly as they did before — the opt-outs preserve current behavior; no rendering changes yet.\n- The question: \"Want to open this as its own PR before we start adopting Cache Components route by route? Or keep going on this branch?\" Wait for the answer.\n\nMoving to step 2 without checking in defeats the point of taking the incremental path.\n\n### direct\n\nSet `cacheComponents: true` and move to [step 2](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time). The build's blocking routes are the work queue.\n\n## step 2: the inner loop, remove opt-outs one feature at a time\n\nA \"feature\" is a single product surface — `app\u002Fsettings\u002Fprofile\u002F**`, `app\u002Fposts\u002F[slug]\u002F**` — not a whole top-level app like `app\u002Fdashboard\u002F**`. Finish one end-to-end before starting the next.\n\nWithin a feature, walk top-down (layouts before pages, root layout first). Removing a layout's opt-out before its descendants exposes the layout's own blocking reads. (Direct: there are no opt-outs to remove — fix each failing route; if a hand-written opt-out on an ancestor shadows it, remove that first.)\n\nA passing build mid-walk doesn't mean the layout is clean. Removing a layout's opt-out while its descendant pages still have theirs keeps the build passing — each page shadows the inherited validation. The layout's actual blocking reads only surface once nothing below it shadows them. Don't call a feature done at the layout boundary.\n\nUse the **with-a-browser** loop unless a browser is genuinely unreachable. The [`next-dev-loop`](#verifying-each-fix-at-runtime) skill is the source of truth for what counts as \"browser available\" and how to install it.\n\n### the loop, with a browser (preferred)\n\nPer route:\n\n- Remove the opt-out (Incremental) or target the failing route (Direct).\n- Reload in dev. Overlay clean? Skip to verify. Overlay still red? Fix.\n- Fix — fetch the docs page linked from the error (`https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fmessages\u002F\u003Cslug>`), apply the recipe from there. The inline overlay text is a summary; the docs page is the source of truth.\n- Verify in the browser. Confirm the visible content on first paint is what you intended in the shell — not stuck on a fallback, not silently streaming everything out of an empty shell.\n- Re-check siblings if the fix touched shared code (a layout, a sidebar component). A shared-shell change can fix the route you're on and break a sibling.\n\n### the loop, build-only (fallback)\n\nUsed when there's no way to drive a browser — CI, sandbox, the user has no `next dev` running and you can't start one. Weaker signal: confirms the build passes and the route prerenders, but not what ended up in the static shell vs streamed.\n\nPer route:\n\n- Remove the opt-out (Incremental) or target the failing route (Direct).\n- Rebuild with `--debug-build-paths app\u002F\u003Croute>\u002F**` (only that route) or `--debug-prerender` (full build, but past the first failure). Route passing? Move on. Still blocking? Fix.\n- Fix — fetch the docs page linked from the error (`https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fmessages\u002F\u003Cslug>`), apply the recipe from there.\n- Re-check siblings if the fix touched shared code.\n- Flag the route as build-only-verified when you hand the feature off. Each `◐` route still needs a browser pass before the feature is done.\n\n### loop notes\n\n- The [three blocker classes from background](#background) often get missed when fixing in place. Caching a downstream fetch (`getThing(id)`) doesn't clear an `await params` at the top of the page body — push the param promise into the `\u003CSuspense>`-wrapped child.\n- Ambiguous calls are user check-ins, not agent judgment. When you're not sure which fix fits, the blocking code looks security-sensitive, or the user might want to keep the route blocking on purpose — read [references\u002Fper-page-decisions.md](.\u002Freferences\u002Fper-page-decisions.md) before editing. Show the route while you ask: the `next-dev-loop` session runs the browser headed, so drive to the page and leave it on screen so the user is looking at the thing they're deciding about, with a screenshot as the fallback when a headed browser isn't possible. \"Should this stay blocking?\" is much easier to answer while looking at the page than at a file path.\n- Don't narrate the refactor with comments. The only comment the codemod (or you) should leave is `\u002F\u002F TODO: Cache Components adoption` on opt-outs, and the user's existing comments. Don't annotate every `\u003CSuspense>` boundary or `\"use cache\"` call with what it does — the code says that. Drop a comment only when the _why_ isn't clear from the code (e.g. a deliberate Block with a reason).\n\nKeep a todo list of the feature's routes. When every route in the feature is clean, move to step 3.\n\n## step 3: verify the feature\n\nChecklist before checking in with the user:\n\n- `next build` completes without blocking-route errors.\n- No bare TODOs in the feature: `grep -rn \"TODO: Cache Components adoption\"` finds both the codemod's opt-out comments and the sync-IO unblocks from the pre-step. Any `instant = false` left behind is a deliberate, documented Block — comment rewritten to a reason (see [references\u002Fper-page-decisions.md](.\u002Freferences\u002Fper-page-decisions.md) → \"when to leave a Block in place\"). Any `await connection()` left behind has been reviewed and kept on purpose, not left over from the pre-step.\n- Each route visited in the browser: confirm the static shell renders first and every `\u003CSuspense>` fallback resolves to its real content. Capture both states if you can — the fallback (mid-stream) and the final paint — so you have a streaming-experience demo to show the user. Throttle the network in the browser if streaming is too fast to observe.\n\nThen check in with the user. Same rule as the pre-step: speak their language. Don't say \"feature-by-feature loop\" or other internal labels; talk about the feature you adopted and what the user will see.\n\n- What you did: which routes you touched, and the user-visible result per route (e.g. \"the post page now streams the article body behind a skeleton while the layout stays static\").\n- What changed: opt-outs removed, fallbacks added, caching boundaries introduced.\n- Show, don't tell. The `next-dev-loop` session runs the browser headed, so drive the route live for the user so they see the static shell → fallback → final content sequence in real time. If you can't drive a live browser, attach the before\u002Fafter screenshots you captured instead.\n- Give them the click-through: a short table of the feature's routes — the URL to open and what to look for (what renders instantly, which fallbacks appear, what streams in) — so they can verify each one themselves.\n- The question: \"Want to open this feature as a PR and move on to the next, or stop here?\" Wait for the answer.\n\n**Trivial features can skip the check-in.** If adopting a feature only meant removing its `\u002F\u002F TODO: Cache Components adoption` opt-out (no `\u003CSuspense>` added, no `'use cache'` introduced, no render order change), the user sees nothing different. Move on to the next feature without stopping; mention it in passing the next time you do check in.\n\nWhen the loop has run on every feature — every remaining `instant = false` sits under a reason comment, `grep -rln \"TODO: Cache Components adoption\" app` returns nothing — point the user at [further reading](#further-reading) if they want to push the experience further, or stop and ship.\n\n### route table glyphs\n\n`ƒ` → `◐` is where adoption usually lands. `◐ (Partial Prerender)` means a static shell prerenders and the request-time content streams in — the goal state for any route that reads `cookies()`, `headers()`, `params`, or `searchParams`. Some routes legitimately stay `ƒ` when they do request-time work through a documented escape hatch (e.g. a layout that uses `await connection()`); the page is no longer _opted out_, it's genuinely dynamic. Don't remove the escape hatch only to chase a `◐`. The inverse holds: `instant = false` does not force a route to be `ƒ`. The glyph reflects what the route does at prerender time, not which validation knobs it exports.\n\n`◐` tells you a shell exists, not what's in it. A `\u003CSuspense>` boundary placed too high (e.g. wrapping the entire page body, or `\u003CSuspense fallback={null}>` around the article content) pushes the visible content out of the static shell into the streamed payload; the build still reports `◐` because _some_ shell prerendered (often only `\u003Chtml>\u003Cbody>` with framework markup). The route table can't tell you what's in the shell; a browser can. If the shell is empty and everything streams, pull the `\u003CSuspense>` boundary down closer to the actual dynamic read.\n\n## further reading\n\nThe work below is optional and lives in the docs — link the user to them and let them decide which to take on next. Don't walk these through inside this skill.\n\n- [Sweep for more instant navigations](.\u002Freferences\u002Fdev-only-validations.md) — an optional follow-up once adoption is done, never required. A passing build is not the last word, because dev validates every route on each page load (simulating both page loads and client navigations) and catches what the build's first-error exit and descendant shadowing skipped. Offer it as the smaller path to instant navigation for a user who doesn't want to adopt Partial Prefetching. Adopting Partial Prefetching (below) runs the same kind of loop and meets these insights anyway, so recommend both and let the user pick which, or whether. The reference is the loop to execute.\n- [`next-partial-prefetching-adoption`](https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-partial-prefetching-adoption) — the follow-up skill that adopts Partial Prefetching: it enables `partialPrefetching` and audits every `\u003CLink prefetch={true}>` against a decision table (or adopts incrementally with the flag off, driven by the `link-prefetch-partial` insight). It sequences this the same way this skill sequences Cache Components, but the insights are dev-only, so it's a browser click-through, not a build loop. Recommended after instant navigation, since those fixes feed directly into how much of each route the shell can prefetch. Concepts live in the [Adopting Partial Prefetching guide](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fadopting-partial-prefetching).\n- [Prevent regressions with e2e tests](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Finstant-navigation#prevent-regressions-with-e2e-tests) — the `@next\u002Fplaywright` [`instant()`](https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fapi-reference\u002Ffile-conventions\u002Froute-segment-config\u002Finstant#testing-instant-navigation) helper asserts on the UI that's available immediately on navigation, so regressions surface in CI. Recommend it once a route is instant: `next-dev-loop` confirms it _now_; an `instant()` test keeps it that way.\n- [`next-cache-components-optimizer`](https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-cache-components-optimizer) — a separate skill that grows each route's static shell so more of the page prerenders and less streams in. Pure optimization, not part of adoption.\n",{"data":51,"body":52},{"name":4,"description":6},{"type":53,"children":54},"root",[55,62,103,109,456,462,582,588,600,605,655,675,680,717,722,900,906,912,931,1031,1058,1064,1069,1074,1313,1319,1339,1351,1401,1406,1411,1557,1577,1629,1664,1720,1760,1819,1831,1836,1868,1896,1983,1989,1994,2045,2050,2055,2073,2079,2107,2112,2117,2139,2145,2150,2185,2191,2203,2207,2263,2269,2359,2364,2370,2375,2433,2438,2473,2505,2533,2539,2631,2685,2690,2695,2826],{"type":56,"tag":57,"props":58,"children":59},"element","h1",{"id":4},[60],{"type":61,"value":4},"text",{"type":56,"tag":63,"props":64,"children":65},"p",{},[66,68,77,79,86,88,94,95,101],{"type":61,"value":67},"Enable Cache Components on an app and walk it to a passing build. This skill sequences the work; per-error recipes live in the dev overlay fix cards and the build's terminal output. The ",{"type":56,"tag":69,"props":70,"children":74},"a",{"href":71,"rel":72},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fmigrating-to-cache-components",[73],"nofollow",[75],{"type":61,"value":76},"migrating to Cache Components guide",{"type":61,"value":78}," is the canonical reference for the concepts and per-API recipes this skill applies — consult it whenever the skill steps reference a pattern (",{"type":56,"tag":80,"props":81,"children":83},"code",{"className":82},[],[84],{"type":61,"value":85},"\"use cache\"",{"type":61,"value":87},", ",{"type":56,"tag":80,"props":89,"children":91},{"className":90},[],[92],{"type":61,"value":93},"cacheLife",{"type":61,"value":87},{"type":56,"tag":80,"props":96,"children":98},{"className":97},[],[99],{"type":61,"value":100},"\u003CSuspense>",{"type":61,"value":102}," placement, etc.) and you want the full explanation.",{"type":56,"tag":104,"props":105,"children":107},"h2",{"id":106},"requires",[108],{"type":61,"value":106},{"type":56,"tag":110,"props":111,"children":112},"ul",{},[113,199,233,311,411],{"type":56,"tag":114,"props":115,"children":116},"li",{},[117,123,125,131,133,139,141,146,148,154,156,162,163,169,171,176,178,183,185,190,192,197],{"type":56,"tag":118,"props":119,"children":120},"strong",{},[121],{"type":61,"value":122},"App Router project.",{"type":61,"value":124}," Cache Components is an App Router feature; ",{"type":56,"tag":80,"props":126,"children":128},{"className":127},[],[129],{"type":61,"value":130},"cacheComponents: true",{"type":61,"value":132}," does nothing for ",{"type":56,"tag":80,"props":134,"children":136},{"className":135},[],[137],{"type":61,"value":138},"pages\u002F",{"type":61,"value":140}," routes. If the project has a ",{"type":56,"tag":80,"props":142,"children":144},{"className":143},[],[145],{"type":61,"value":138},{"type":61,"value":147}," or ",{"type":56,"tag":80,"props":149,"children":151},{"className":150},[],[152],{"type":61,"value":153},"src\u002Fpages\u002F",{"type":61,"value":155}," tree but no ",{"type":56,"tag":80,"props":157,"children":159},{"className":158},[],[160],{"type":61,"value":161},"app\u002F",{"type":61,"value":147},{"type":56,"tag":80,"props":164,"children":166},{"className":165},[],[167],{"type":61,"value":168},"src\u002Fapp\u002F",{"type":61,"value":170}," tree, stop and tell the user — Pages → App migration is its own project, not part of this skill. A hybrid app (both ",{"type":56,"tag":80,"props":172,"children":174},{"className":173},[],[175],{"type":61,"value":138},{"type":61,"value":177}," and ",{"type":56,"tag":80,"props":179,"children":181},{"className":180},[],[182],{"type":61,"value":161},{"type":61,"value":184},") is fine: the flag affects the ",{"type":56,"tag":80,"props":186,"children":188},{"className":187},[],[189],{"type":61,"value":161},{"type":61,"value":191}," routes; ",{"type":56,"tag":80,"props":193,"children":195},{"className":194},[],[196],{"type":61,"value":138},{"type":61,"value":198}," routes are unaffected and don't need opt-outs.",{"type":56,"tag":114,"props":200,"children":201},{},[202,207,209,215,217,223,225,231],{"type":56,"tag":118,"props":203,"children":204},{},[205],{"type":61,"value":206},"A runnable app.",{"type":61,"value":208}," The whole loop verifies against ",{"type":56,"tag":80,"props":210,"children":212},{"className":211},[],[213],{"type":61,"value":214},"next dev",{"type":61,"value":216}," and a browser, so the app has to boot. If it reads a database or required env at import (e.g. an ",{"type":56,"tag":80,"props":218,"children":220},{"className":219},[],[221],{"type":61,"value":222},"env.ts",{"type":61,"value":224}," that throws on a missing ",{"type":56,"tag":80,"props":226,"children":228},{"className":227},[],[229],{"type":61,"value":230},"DATABASE_URL",{"type":61,"value":232},"), confirm it actually starts — with the real environment, or local data you stand up — before step 1. Adoption can't be verified against an app that won't run.",{"type":56,"tag":114,"props":234,"children":235},{},[236,241,243,249,250,256,258,264,266,272,274],{"type":56,"tag":118,"props":237,"children":238},{},[239],{"type":61,"value":240},"Next.js 16.3 or later.",{"type":61,"value":242}," That release is where the pieces this skill relies on land: top-level ",{"type":56,"tag":80,"props":244,"children":246},{"className":245},[],[247],{"type":61,"value":248},"cacheComponents",{"type":61,"value":87},{"type":56,"tag":80,"props":251,"children":253},{"className":252},[],[254],{"type":61,"value":255},"export const instant",{"type":61,"value":257},", the dev-overlay instant-navigation validation warnings, and the ",{"type":56,"tag":80,"props":259,"children":261},{"className":260},[],[262],{"type":61,"value":263},"cache-components-instant-false",{"type":61,"value":265}," codemod. If ",{"type":56,"tag":80,"props":267,"children":269},{"className":268},[],[270],{"type":61,"value":271},"next --version",{"type":61,"value":273}," reports below 16.3, upgrade first:",{"type":56,"tag":110,"props":275,"children":276},{},[277,288],{"type":56,"tag":114,"props":278,"children":279},{},[280,286],{"type":56,"tag":80,"props":281,"children":283},{"className":282},[],[284],{"type":61,"value":285},"npx @next\u002Fcodemod@latest upgrade latest",{"type":61,"value":287}," to apply the version-to-version codemods.",{"type":56,"tag":114,"props":289,"children":290},{},[291,293,300,302,309],{"type":61,"value":292},"Read the relevant ",{"type":56,"tag":69,"props":294,"children":297},{"href":295,"rel":296},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fupgrading",[73],[298],{"type":61,"value":299},"version upgrade guide",{"type":61,"value":301}," (e.g. ",{"type":56,"tag":69,"props":303,"children":306},{"href":304,"rel":305},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fupgrading\u002Fversion-16",[73],[307],{"type":61,"value":308},"Version 16",{"type":61,"value":310},") for what the codemod doesn't cover.",{"type":56,"tag":114,"props":312,"children":313},{},[314,319,321,326,328,334,335,341,343,349,351,356,358,365,367,373,375,381,383,388,389,394,396,402,404,409],{"type":56,"tag":118,"props":315,"children":316},{},[317],{"type":61,"value":318},"No incompatible config keys.",{"type":61,"value":320}," ",{"type":56,"tag":80,"props":322,"children":324},{"className":323},[],[325],{"type":61,"value":130},{"type":61,"value":327}," errors on any file that still exports ",{"type":56,"tag":80,"props":329,"children":331},{"className":330},[],[332],{"type":61,"value":333},"dynamic",{"type":61,"value":87},{"type":56,"tag":80,"props":336,"children":338},{"className":337},[],[339],{"type":61,"value":340},"revalidate",{"type":61,"value":342},", or ",{"type":56,"tag":80,"props":344,"children":346},{"className":345},[],[347],{"type":61,"value":348},"fetchCache",{"type":61,"value":350},". ",{"type":56,"tag":118,"props":352,"children":353},{},[354],{"type":61,"value":355},"Translate, don't delete.",{"type":61,"value":357}," Each export encodes behavior the route needs to keep doing; migrate each one to its Cache Components equivalent via the ",{"type":56,"tag":69,"props":359,"children":362},{"href":360,"rel":361},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fmigrating-to-cache-components#enable-cache-components",[73],[363],{"type":61,"value":364},"migration guide's per-key sections",{"type":61,"value":366},". The exception is ",{"type":56,"tag":80,"props":368,"children":370},{"className":369},[],[371],{"type":61,"value":372},"dynamic = 'force-dynamic'",{"type":61,"value":374},": under Cache Components every route is already dynamic by default, so the migration guide removes it outright rather than translating it — don't overthink a batch of identical ",{"type":56,"tag":80,"props":376,"children":378},{"className":377},[],[379],{"type":61,"value":380},"force-dynamic",{"type":61,"value":382}," deletions. ",{"type":56,"tag":80,"props":384,"children":386},{"className":385},[],[387],{"type":61,"value":340},{"type":61,"value":177},{"type":56,"tag":80,"props":390,"children":392},{"className":391},[],[393],{"type":61,"value":348},{"type":61,"value":395}," still need real translation. If a value can't be cleanly translated yet, leave a ",{"type":56,"tag":80,"props":397,"children":399},{"className":398},[],[400],{"type":61,"value":401},"\u002F\u002F TODO: Cache Components adoption — restore revalidate = 3600",{"type":61,"value":403}," comment so the loop picks it up. The ",{"type":56,"tag":80,"props":405,"children":407},{"className":406},[],[408],{"type":61,"value":263},{"type":61,"value":410}," codemod does not touch these.",{"type":56,"tag":114,"props":412,"children":413},{},[414,425,427,432,434,439,441,447,449,454],{"type":56,"tag":118,"props":415,"children":416},{},[417,423],{"type":56,"tag":80,"props":418,"children":420},{"className":419},[],[421],{"type":61,"value":422},"experimental.dynamicIO",{"type":61,"value":424}," is fatal.",{"type":61,"value":426}," It was renamed to top-level ",{"type":56,"tag":80,"props":428,"children":430},{"className":429},[],[431],{"type":61,"value":248},{"type":61,"value":433}," and the old key now aborts before any build can run — remove it (or replace with ",{"type":56,"tag":80,"props":435,"children":437},{"className":436},[],[438],{"type":61,"value":130},{"type":61,"value":440},") first. ",{"type":56,"tag":80,"props":442,"children":444},{"className":443},[],[445],{"type":61,"value":446},"experimental.useCache",{"type":61,"value":448}," is still accepted as a deprecated alias; redundant once ",{"type":56,"tag":80,"props":450,"children":452},{"className":451},[],[453],{"type":61,"value":130},{"type":61,"value":455}," is set, so remove it for clarity.",{"type":56,"tag":457,"props":458,"children":460},"h3",{"id":459},"notes",[461],{"type":61,"value":459},{"type":56,"tag":110,"props":463,"children":464},{},[465,498,540],{"type":56,"tag":114,"props":466,"children":467},{},[468,473,475,480,482,488,490,496],{"type":56,"tag":118,"props":469,"children":470},{},[471],{"type":61,"value":472},"No passing baseline before the flag.",{"type":61,"value":474}," If the app already uses ",{"type":56,"tag":80,"props":476,"children":478},{"className":477},[],[479],{"type":61,"value":85},{"type":61,"value":481},", the pre-flag build errors with ",{"type":56,"tag":80,"props":483,"children":485},{"className":484},[],[486],{"type":61,"value":487},"please enable the feature flag cacheComponents",{"type":61,"value":489},". Enabling the flag is the first thing you do (in Incremental, before the codemod; in Direct, before fixing routes) — not a thing to do ",{"type":56,"tag":491,"props":492,"children":493},"em",{},[494],{"type":61,"value":495},"after",{"type":61,"value":497}," getting a passing build. Note this in your starting summary so it doesn't read as a regression.",{"type":56,"tag":114,"props":499,"children":500},{},[501,506,508,514,516,522,524,530,532,538],{"type":56,"tag":118,"props":502,"children":503},{},[504],{"type":61,"value":505},"Offline docs.",{"type":61,"value":507}," Guide links have offline copies under ",{"type":56,"tag":80,"props":509,"children":511},{"className":510},[],[512],{"type":61,"value":513},"node_modules\u002Fnext\u002Fdist\u002Fdocs\u002F",{"type":61,"value":515}," (bundled since Next.js 16.2), with the directory layout numbered for ordering (e.g. ",{"type":56,"tag":80,"props":517,"children":519},{"className":518},[],[520],{"type":61,"value":521},"node_modules\u002Fnext\u002Fdist\u002Fdocs\u002F01-app\u002F02-guides\u002Fmigrating-to-cache-components.md",{"type":61,"value":523},"). If you can't predict the numbered prefix, ",{"type":56,"tag":80,"props":525,"children":527},{"className":526},[],[528],{"type":61,"value":529},"find node_modules\u002Fnext\u002Fdist\u002Fdocs -name '\u003Cslug>.md'",{"type":61,"value":531}," resolves it. The ",{"type":56,"tag":80,"props":533,"children":535},{"className":534},[],[536],{"type":61,"value":537},"\u002Fdocs\u002Fmessages\u002F*",{"type":61,"value":539}," error pages are not bundled.",{"type":56,"tag":114,"props":541,"children":542},{},[543,548,550,556,558,564,566,572,574,580],{"type":56,"tag":118,"props":544,"children":545},{},[546],{"type":61,"value":547},"Older versions without bundled docs.",{"type":61,"value":549}," Suggest ",{"type":56,"tag":80,"props":551,"children":553},{"className":552},[],[554],{"type":61,"value":555},"npx @next\u002Fcodemod@latest agents-md",{"type":61,"value":557}," to the user before starting: it downloads a version-matched copy to ",{"type":56,"tag":80,"props":559,"children":561},{"className":560},[],[562],{"type":61,"value":563},".next-docs\u002F",{"type":61,"value":565}," and writes an index into ",{"type":56,"tag":80,"props":567,"children":569},{"className":568},[],[570],{"type":61,"value":571},"AGENTS.md",{"type":61,"value":573}," \u002F ",{"type":56,"tag":80,"props":575,"children":577},{"className":576},[],[578],{"type":61,"value":579},"CLAUDE.md",{"type":61,"value":581},". It touches files in their repo, so ask first and run it only if they want it.",{"type":56,"tag":104,"props":583,"children":585},{"id":584},"the-shape-of-the-work",[586],{"type":61,"value":587},"the shape of the work",{"type":56,"tag":63,"props":589,"children":590},{},[591,593,598],{"type":61,"value":592},"There's one loop: walk the route tree top-down, one feature at a time, adopting each route against ",{"type":56,"tag":80,"props":594,"children":596},{"className":595},[],[597],{"type":61,"value":214},{"type":61,"value":599}," + a browser. The build is a final check for each feature, not the working surface.",{"type":56,"tag":63,"props":601,"children":602},{},[603],{"type":61,"value":604},"The choice in step 1 is whether to opt every route out of validation first or fix routes as you go. Either way the loop is the same:",{"type":56,"tag":110,"props":606,"children":607},{},[608,638],{"type":56,"tag":114,"props":609,"children":610},{},[611,616,618,623,625,630,631,636],{"type":56,"tag":118,"props":612,"children":613},{},[614],{"type":61,"value":615},"With a quiet pre-step (Incremental).",{"type":61,"value":617}," Run the codemod to opt every page and layout out of validation. Once you've also fixed what the codemod can't (sync-IO calls, leftover ",{"type":56,"tag":80,"props":619,"children":621},{"className":620},[],[622],{"type":61,"value":340},{"type":61,"value":624},"\u002F",{"type":56,"tag":80,"props":626,"children":628},{"className":627},[],[629],{"type":61,"value":333},{"type":61,"value":624},{"type":56,"tag":80,"props":632,"children":634},{"className":633},[],[635],{"type":61,"value":348},{"type":61,"value":637}," exports), the build passes; you ship that as its own PR and then start the loop — removing one opt-out at a time and adopting that route. This splits the work into small, reviewable PRs.",{"type":56,"tag":114,"props":639,"children":640},{},[641,646,648,653],{"type":56,"tag":118,"props":642,"children":643},{},[644],{"type":61,"value":645},"Without (Direct).",{"type":61,"value":647}," Enable ",{"type":56,"tag":80,"props":649,"children":651},{"className":650},[],[652],{"type":61,"value":248},{"type":61,"value":654}," and start the loop on whatever the build flags first. Same loop, but every fix sits on one branch until adoption is complete.",{"type":56,"tag":63,"props":656,"children":657},{},[658,660,673],{"type":61,"value":659},"In both, the per-route success bar is the same: ",{"type":56,"tag":118,"props":661,"children":662},{},[663,665,671],{"type":61,"value":664},"dev loop reports no errors AND ",{"type":56,"tag":80,"props":666,"children":668},{"className":667},[],[669],{"type":61,"value":670},"next build",{"type":61,"value":672}," passes",{"type":61,"value":674},". Check in with the user after every feature, and suggest a commit but never make one without their confirmation. Expect to spend most of the time in the loop, not in the pre-step.",{"type":56,"tag":104,"props":676,"children":678},{"id":677},"background",[679],{"type":61,"value":677},{"type":56,"tag":63,"props":681,"children":682},{},[683,688,690,695,697,703,705,715],{"type":56,"tag":80,"props":684,"children":686},{"className":685},[],[687],{"type":61,"value":130},{"type":61,"value":689}," requires every route to be prerenderable. A route that reads request-time data outside ",{"type":56,"tag":80,"props":691,"children":693},{"className":692},[],[694],{"type":61,"value":100},{"type":61,"value":696}," is \"blocking\" and fails the build. ",{"type":56,"tag":80,"props":698,"children":700},{"className":699},[],[701],{"type":61,"value":702},"export const instant = false",{"type":61,"value":704}," marks a route as allowed to block, which clears it in both dev and build; on a layout it covers the whole subtree during the build, but client navigations still validate each descendant segment on its own. Reads wrapped in a ",{"type":56,"tag":69,"props":706,"children":709},{"href":707,"rel":708},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fapi-reference\u002Fdirectives\u002Fuse-cache",[73],[710],{"type":56,"tag":80,"props":711,"children":713},{"className":712},[],[714],{"type":61,"value":85},{"type":61,"value":716}," function count as cache boundaries, not blocking reads.",{"type":56,"tag":63,"props":718,"children":719},{},[720],{"type":61,"value":721},"Three classes of blocker come up, usually in this order:",{"type":56,"tag":723,"props":724,"children":725},"ol",{},[726,808,862],{"type":56,"tag":114,"props":727,"children":728},{},[729,734,736,742,743,749,750,756,757,763,765,771,772,778,780,785,787,792,793,798,800,806],{"type":56,"tag":118,"props":730,"children":731},{},[732],{"type":61,"value":733},"Request-time reads",{"type":61,"value":735}," (",{"type":56,"tag":80,"props":737,"children":739},{"className":738},[],[740],{"type":61,"value":741},"cookies()",{"type":61,"value":87},{"type":56,"tag":80,"props":744,"children":746},{"className":745},[],[747],{"type":61,"value":748},"headers()",{"type":61,"value":87},{"type":56,"tag":80,"props":751,"children":753},{"className":752},[],[754],{"type":61,"value":755},"await params",{"type":61,"value":87},{"type":56,"tag":80,"props":758,"children":760},{"className":759},[],[761],{"type":61,"value":762},"await searchParams",{"type":61,"value":764},"). All four block when awaited at the top of a page or layout. ",{"type":56,"tag":80,"props":766,"children":768},{"className":767},[],[769],{"type":61,"value":770},"params",{"type":61,"value":177},{"type":56,"tag":80,"props":773,"children":775},{"className":774},[],[776],{"type":61,"value":777},"searchParams",{"type":61,"value":779}," often get missed because they're not framed as \"request data\" the way cookies and headers are. The fix is to push the read into a ",{"type":56,"tag":80,"props":781,"children":783},{"className":782},[],[784],{"type":61,"value":100},{"type":61,"value":786},"-wrapped child — and for ",{"type":56,"tag":80,"props":788,"children":790},{"className":789},[],[791],{"type":61,"value":770},{"type":61,"value":624},{"type":56,"tag":80,"props":794,"children":796},{"className":795},[],[797],{"type":61,"value":777},{"type":61,"value":799},", forward the promise into the child and await it there; don't ",{"type":56,"tag":80,"props":801,"children":803},{"className":802},[],[804],{"type":61,"value":805},"await",{"type":61,"value":807}," at the page top.",{"type":56,"tag":114,"props":809,"children":810},{},[811,816,817,823,824,830,831,837,838,844,846,852,854,860],{"type":56,"tag":118,"props":812,"children":813},{},[814],{"type":61,"value":815},"Sync-IO at module\u002Frender time",{"type":61,"value":735},{"type":56,"tag":80,"props":818,"children":820},{"className":819},[],[821],{"type":61,"value":822},"new Date()",{"type":61,"value":87},{"type":56,"tag":80,"props":825,"children":827},{"className":826},[],[828],{"type":61,"value":829},"Date.now()",{"type":61,"value":87},{"type":56,"tag":80,"props":832,"children":834},{"className":833},[],[835],{"type":61,"value":836},"Math.random()",{"type":61,"value":87},{"type":56,"tag":80,"props":839,"children":841},{"className":840},[],[842],{"type":61,"value":843},"crypto.randomUUID()",{"type":61,"value":845},"). These fail the build even with ",{"type":56,"tag":80,"props":847,"children":849},{"className":848},[],[850],{"type":61,"value":851},"instant = false",{"type":61,"value":853}," — the opt-out doesn't suppress them. If they're in a shared layout, they block every route under it. The codemod can't fix them; you have to translate each one by hand before the build can pass (see the ",{"type":56,"tag":69,"props":855,"children":857},{"href":856},"#incremental",[858],{"type":61,"value":859},"incremental pre-step",{"type":61,"value":861},"). Grep the whole repo for these calls before running anything else.",{"type":56,"tag":114,"props":863,"children":864},{},[865,875,877,882,884,890,892,898],{"type":56,"tag":118,"props":866,"children":867},{},[868,873],{"type":56,"tag":80,"props":869,"children":871},{"className":870},[],[872],{"type":61,"value":85},{"type":61,"value":874}," files that read request data.",{"type":61,"value":876}," A file with a top-level ",{"type":56,"tag":80,"props":878,"children":880},{"className":879},[],[881],{"type":61,"value":85},{"type":61,"value":883}," directive can't export ",{"type":56,"tag":80,"props":885,"children":887},{"className":886},[],[888],{"type":61,"value":889},"instant",{"type":61,"value":891},"; combining the two errors with ",{"type":56,"tag":80,"props":893,"children":895},{"className":894},[],[896],{"type":61,"value":897},"Only async functions are allowed to be exported in a \"use cache\" file.",{"type":61,"value":899},", which means the directive was wrong for that route. Remove it before running the codemod.",{"type":56,"tag":104,"props":901,"children":903},{"id":902},"working-surfaces",[904],{"type":61,"value":905},"working surfaces",{"type":56,"tag":457,"props":907,"children":909},{"id":908},"finding-blocking-routes",[910],{"type":61,"value":911},"finding blocking routes",{"type":56,"tag":63,"props":913,"children":914},{},[915,917,922,924,929],{"type":61,"value":916},"Prefer ",{"type":56,"tag":80,"props":918,"children":920},{"className":919},[],[921],{"type":61,"value":214},{"type":61,"value":923}," over ",{"type":56,"tag":80,"props":925,"children":927},{"className":926},[],[928],{"type":61,"value":670},{"type":61,"value":930}," while you work.",{"type":56,"tag":110,"props":932,"children":933},{},[934,963],{"type":56,"tag":114,"props":935,"children":936},{},[937,945,947,953,955,961],{"type":56,"tag":118,"props":938,"children":939},{},[940],{"type":56,"tag":80,"props":941,"children":943},{"className":942},[],[944],{"type":61,"value":214},{"type":61,"value":946}," — the working surface. Visit a route; its blocking errors surface in the dev overlay with full stack traces and fix cards linking the per-error docs. Work one route at a time — errors don't accumulate in one place. The route itself still returns HTTP 200, so read the overlay (or ",{"type":56,"tag":80,"props":948,"children":950},{"className":949},[],[951],{"type":61,"value":952},".next-dev.log",{"type":61,"value":954},"), not status codes. A cleared overlay is one half of calling a route clean — the other half is browser verification (see ",{"type":56,"tag":69,"props":956,"children":958},{"href":957},"#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time",[959],{"type":61,"value":960},"step 2",{"type":61,"value":962},") and a passing build for that route.",{"type":56,"tag":114,"props":964,"children":965},{},[966,974,976,981,983,989,991,997,999,1005,1007,1013,1015,1021,1023,1029],{"type":56,"tag":118,"props":967,"children":968},{},[969],{"type":56,"tag":80,"props":970,"children":972},{"className":971},[],[973],{"type":61,"value":670},{"type":61,"value":975}," — detection only. The build is ",{"type":56,"tag":80,"props":977,"children":979},{"className":978},[],[980],{"type":61,"value":214},{"type":61,"value":982},"'s authoritative check, not its replacement. Use it as the last gate on each feature in the loop (a passing build is part of the per-route success bar) and as the final verification across the whole app. In Incremental, the build also confirms the pre-step (codemod opted every route out, no shared layout still has a sync-IO blocker) before you ship that PR. Don't reach for the build instead of the dev loop while you're working a route — a passing compile doesn't tell you what ended up in the static shell and what streamed. By default the build stops at the first blocking route, so it's also poor for sizing the work. Two flags help when iterating: ",{"type":56,"tag":80,"props":984,"children":986},{"className":985},[],[987],{"type":61,"value":988},"--debug-build-paths",{"type":61,"value":990}," builds only the routes you name (comma-separated glob patterns of file paths relative to the project root, e.g. ",{"type":56,"tag":80,"props":992,"children":994},{"className":993},[],[995],{"type":61,"value":996},"--debug-build-paths=\"app\u002Fadmin\u002F**\u002Fpage.tsx\"",{"type":61,"value":998}," — not URL paths; ",{"type":56,"tag":80,"props":1000,"children":1002},{"className":1001},[],[1003],{"type":61,"value":1004},"--debug-build-paths=\"app\u002F(marketing)\u002Fabout\u002Fpage.tsx\"",{"type":61,"value":1006}," — not ",{"type":56,"tag":80,"props":1008,"children":1010},{"className":1009},[],[1011],{"type":61,"value":1012},"\u002Fabout",{"type":61,"value":1014},"; ",{"type":56,"tag":80,"props":1016,"children":1018},{"className":1017},[],[1019],{"type":61,"value":1020},"--debug-build-paths=\"app\u002Fadmin\"",{"type":61,"value":1022}," matches nothing and silently builds zero routes), and ",{"type":56,"tag":80,"props":1024,"children":1026},{"className":1025},[],[1027],{"type":61,"value":1028},"--debug-prerender",{"type":61,"value":1030}," disables the early exit so the build continues past the first prerender failure, reports every blocking route, and prints a fuller stack trace that names the originating file and line.",{"type":56,"tag":63,"props":1032,"children":1033},{},[1034,1036,1042,1044,1049,1051,1056],{"type":61,"value":1035},"Every blocking error has a docs page — open it. Both the dev overlay and the build terminal print a ",{"type":56,"tag":80,"props":1037,"children":1039},{"className":1038},[],[1040],{"type":61,"value":1041},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fmessages\u002F\u003Cslug>",{"type":61,"value":1043}," link with each error. That page is the canonical recipe for the fix; the inline message is a summary. Fetch the link for every distinct error you encounter, even if you think you know the pattern — the recipes evolve, and the same error class can have different correct fixes depending on what the route reads. Don't improvise from the inline message alone. (",{"type":56,"tag":80,"props":1045,"children":1047},{"className":1046},[],[1048],{"type":61,"value":537},{"type":61,"value":1050}," pages aren't bundled offline; if you have no network, fall back to the per-API guides under ",{"type":56,"tag":80,"props":1052,"children":1054},{"className":1053},[],[1055],{"type":61,"value":513},{"type":61,"value":1057}," and note the limitation when you report back.)",{"type":56,"tag":457,"props":1059,"children":1061},{"id":1060},"verifying-each-fix-at-runtime",[1062],{"type":61,"value":1063},"verifying each fix at runtime",{"type":56,"tag":63,"props":1065,"children":1066},{},[1067],{"type":61,"value":1068},"A passing build or a cleared overlay isn't proof the route actually behaves — Cache Components is a runtime concern (a static shell with streamed data). Verify after every fix, not only at the end.",{"type":56,"tag":63,"props":1070,"children":1071},{},[1072],{"type":61,"value":1073},"In preference order:",{"type":56,"tag":723,"props":1075,"children":1076},{},[1077,1239,1270,1303],{"type":56,"tag":114,"props":1078,"children":1079},{},[1080,1096,1098,1104,1106,1112,1114,1119,1121,1125,1127,1132,1134,1173,1176,1178,1183,1185,1188,1193,1195,1201,1203,1209,1211,1217,1219,1225,1227,1230,1232,1237],{"type":56,"tag":118,"props":1081,"children":1082},{},[1083,1094],{"type":56,"tag":69,"props":1084,"children":1087},{"href":1085,"rel":1086},"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-dev-loop",[73],[1088],{"type":56,"tag":80,"props":1089,"children":1091},{"className":1090},[],[1092],{"type":61,"value":1093},"next-dev-loop",{"type":61,"value":1095}," — strongly preferred.",{"type":61,"value":1097}," Cross-checks ",{"type":56,"tag":80,"props":1099,"children":1101},{"className":1100},[],[1102],{"type":61,"value":1103},"\u002F_next\u002Fmcp",{"type":61,"value":1105}," against the live browser via ",{"type":56,"tag":80,"props":1107,"children":1109},{"className":1108},[],[1110],{"type":61,"value":1111},"agent-browser",{"type":61,"value":1113}," and surfaces both compile and runtime issues in one pass. The diagnostics (React tree, suspense boundaries, console + network) are richer than poking at ",{"type":56,"tag":80,"props":1115,"children":1117},{"className":1116},[],[1118],{"type":61,"value":214},{"type":61,"value":1120}," by hand.",{"type":56,"tag":1122,"props":1123,"children":1124},"br",{},[],{"type":61,"value":1126},"Install it before starting the loop. Don't wait until you hit something ",{"type":56,"tag":80,"props":1128,"children":1130},{"className":1129},[],[1131],{"type":61,"value":214},{"type":61,"value":1133}," alone can't explain. Run:",{"type":56,"tag":1135,"props":1136,"children":1141},"pre",{"className":1137,"code":1138,"language":1139,"meta":1140,"style":1140},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx skills add https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-dev-loop\n","bash","",[1142],{"type":56,"tag":80,"props":1143,"children":1144},{"__ignoreMap":1140},[1145],{"type":56,"tag":1146,"props":1147,"children":1150},"span",{"class":1148,"line":1149},"line",1,[1151,1157,1163,1168],{"type":56,"tag":1146,"props":1152,"children":1154},{"style":1153},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[1155],{"type":61,"value":1156},"npx",{"type":56,"tag":1146,"props":1158,"children":1160},{"style":1159},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[1161],{"type":61,"value":1162}," skills",{"type":56,"tag":1146,"props":1164,"children":1165},{"style":1159},[1166],{"type":61,"value":1167}," add",{"type":56,"tag":1146,"props":1169,"children":1170},{"style":1159},[1171],{"type":61,"value":1172}," https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-dev-loop\n",{"type":56,"tag":1122,"props":1174,"children":1175},{},[],{"type":61,"value":1177},"The skill states its required ",{"type":56,"tag":80,"props":1179,"children":1181},{"className":1180},[],[1182],{"type":61,"value":1111},{"type":61,"value":1184}," version and walks you through it.",{"type":56,"tag":1122,"props":1186,"children":1187},{},[],{"type":56,"tag":118,"props":1189,"children":1190},{},[1191],{"type":61,"value":1192},"Requires Turbopack.",{"type":61,"value":1194}," If ",{"type":56,"tag":80,"props":1196,"children":1198},{"className":1197},[],[1199],{"type":61,"value":1200},"package.json",{"type":61,"value":1202},"'s ",{"type":56,"tag":80,"props":1204,"children":1206},{"className":1205},[],[1207],{"type":61,"value":1208},"dev",{"type":61,"value":1210}," script passes ",{"type":56,"tag":80,"props":1212,"children":1214},{"className":1213},[],[1215],{"type":61,"value":1216},"--webpack",{"type":61,"value":1218},", flag it to the user and ask whether there's a reason to stay on webpack. If not, switch to Turbopack (the Next.js 16.3+ default). If they want to keep webpack, skip this install and use the ",{"type":56,"tag":69,"props":1220,"children":1222},{"href":1221},"#the-loop-build-only-fallback",[1223],{"type":61,"value":1224},"build-only loop",{"type":61,"value":1226}," instead.",{"type":56,"tag":1122,"props":1228,"children":1229},{},[],{"type":61,"value":1231},"You don't need permission to install ",{"type":56,"tag":80,"props":1233,"children":1235},{"className":1234},[],[1236],{"type":61,"value":1093},{"type":61,"value":1238}," itself. It's a tool, like installing a dev dependency. If a user is present, briefly tell them you're installing it for verification. In a non-interactive run (CI, dashboard, sandbox), install it without asking — \"can't prompt the user\" is not a reason to skip. The only legitimate skip is a real technical blocker: no network, no npm, read-only filesystem, a stated no-new-deps policy, or a webpack-only dev script. If you skip, name the specific blocker in your final report.",{"type":56,"tag":114,"props":1240,"children":1241},{},[1242,1247,1249,1254,1256,1261,1263,1268],{"type":56,"tag":118,"props":1243,"children":1244},{},[1245],{"type":61,"value":1246},"A browser you can drive yourself.",{"type":61,"value":1248}," Playwright, ",{"type":56,"tag":80,"props":1250,"children":1252},{"className":1251},[],[1253],{"type":61,"value":1111},{"type":61,"value":1255}," directly, any browser-automation tool. Use only when ",{"type":56,"tag":80,"props":1257,"children":1259},{"className":1258},[],[1260],{"type":61,"value":1093},{"type":61,"value":1262}," is genuinely blocked. You'll miss the framework-side checks (",{"type":56,"tag":80,"props":1264,"children":1266},{"className":1265},[],[1267],{"type":61,"value":1103},{"type":61,"value":1269},"), so DOM assertions alone don't catch every regression — be more cautious about what you call \"verified.\"",{"type":56,"tag":114,"props":1271,"children":1272},{},[1273,1278,1280,1286,1288,1293,1295,1301],{"type":56,"tag":118,"props":1274,"children":1275},{},[1276],{"type":61,"value":1277},"Build-only.",{"type":61,"value":1279}," If you can't run a dev server at all, the build is your only signal. ",{"type":56,"tag":80,"props":1281,"children":1283},{"className":1282},[],[1284],{"type":61,"value":1285},"○ (Static)",{"type":61,"value":1287}," routes with no ",{"type":56,"tag":80,"props":1289,"children":1291},{"className":1290},[],[1292],{"type":61,"value":100},{"type":61,"value":1294}," are fully verified by the build (nothing streamed to test). ",{"type":56,"tag":80,"props":1296,"children":1298},{"className":1297},[],[1299],{"type":61,"value":1300},"◐ (Partial Prerender)",{"type":61,"value":1302}," routes are only shell-verified — flag them when you report back.",{"type":56,"tag":114,"props":1304,"children":1305},{},[1306,1311],{"type":56,"tag":118,"props":1307,"children":1308},{},[1309],{"type":61,"value":1310},"No tooling at all.",{"type":61,"value":1312}," Ask the user to run the dev server (or build) and report what they see, or hand off the milestone you've reached.",{"type":56,"tag":104,"props":1314,"children":1316},{"id":1315},"step-1-choose-a-strategy",[1317],{"type":61,"value":1318},"step 1: choose a strategy",{"type":56,"tag":63,"props":1320,"children":1321},{},[1322,1324,1329,1331,1337],{"type":61,"value":1323},"Ask the user, in terms of the PRs they want, not the size of the job. Never use the internal labels (Incremental, Direct) when talking to the user — those are your own scaffolding. Ask in terms of PRs and features, e.g.: ",{"type":56,"tag":491,"props":1325,"children":1326},{},[1327],{"type":61,"value":1328},"\"Do you want me to first open a PR that turns on Cache Components and opts every route out of validation, then handle the actual route adoptions feature-by-feature in follow-up PRs? Or do everything on one branch?\"",{"type":61,"value":1330}," Even on a tiny app, the incremental path still has value (review-sized PR, revertible, the ",{"type":56,"tag":80,"props":1332,"children":1334},{"className":1333},[],[1335],{"type":61,"value":1336},"\u002F\u002F TODO: Cache Components adoption",{"type":61,"value":1338}," markers double as your work queue for next session). Don't pick on their behalf.",{"type":56,"tag":63,"props":1340,"children":1341},{},[1342,1344,1349],{"type":61,"value":1343},"If there's no user to ask, default to ",{"type":56,"tag":118,"props":1345,"children":1346},{},[1347],{"type":61,"value":1348},"Incremental",{"type":61,"value":1350}," and document the choice.",{"type":56,"tag":110,"props":1352,"children":1353},{},[1354,1378],{"type":56,"tag":114,"props":1355,"children":1356},{},[1357,1361,1363,1369,1371,1376],{"type":56,"tag":118,"props":1358,"children":1359},{},[1360],{"type":61,"value":1348},{"type":61,"value":1362}," — quiet pre-step + the loop. Run the codemod to opt every page and layout out of validation, get the build passing, stop and check in with the user (see ",{"type":56,"tag":69,"props":1364,"children":1366},{"href":1365},"#end-of-the-pre-step-check-in",[1367],{"type":61,"value":1368},"end of the pre-step",{"type":61,"value":1370},"), then enter ",{"type":56,"tag":69,"props":1372,"children":1373},{"href":957},[1374],{"type":61,"value":1375},"step 2's loop",{"type":61,"value":1377}," and ship each feature as a follow-up PR.",{"type":56,"tag":114,"props":1379,"children":1380},{},[1381,1386,1388,1393,1395,1399],{"type":56,"tag":118,"props":1382,"children":1383},{},[1384],{"type":61,"value":1385},"Direct",{"type":61,"value":1387}," — skip the pre-step. Enable ",{"type":56,"tag":80,"props":1389,"children":1391},{"className":1390},[],[1392],{"type":61,"value":248},{"type":61,"value":1394}," and go straight to ",{"type":56,"tag":69,"props":1396,"children":1397},{"href":957},[1398],{"type":61,"value":1375},{"type":61,"value":1400},"; the build's blocking routes are the work queue.",{"type":56,"tag":457,"props":1402,"children":1404},{"id":1403},"incremental",[1405],{"type":61,"value":1403},{"type":56,"tag":63,"props":1407,"children":1408},{},[1409],{"type":61,"value":1410},"Before invoking the codemod, fix the two classes of blocker it can't.",{"type":56,"tag":723,"props":1412,"children":1413},{},[1414,1525],{"type":56,"tag":114,"props":1415,"children":1416},{},[1417,1422,1424,1429,1430,1435,1436,1441,1443,1448,1450,1456,1458,1464,1466,1471,1473,1479,1481,1486,1488,1505,1508,1510,1516,1518,1523],{"type":56,"tag":118,"props":1418,"children":1419},{},[1420],{"type":61,"value":1421},"Sync-IO at module\u002Frender time.",{"type":61,"value":1423}," Grep the whole repo for ",{"type":56,"tag":80,"props":1425,"children":1427},{"className":1426},[],[1428],{"type":61,"value":822},{"type":61,"value":87},{"type":56,"tag":80,"props":1431,"children":1433},{"className":1432},[],[1434],{"type":61,"value":829},{"type":61,"value":87},{"type":56,"tag":80,"props":1437,"children":1439},{"className":1438},[],[1440],{"type":61,"value":836},{"type":61,"value":1442},", and ",{"type":56,"tag":80,"props":1444,"children":1446},{"className":1445},[],[1447],{"type":61,"value":843},{"type":61,"value":1449}," (not only ",{"type":56,"tag":80,"props":1451,"children":1453},{"className":1452},[],[1454],{"type":61,"value":1455},"app\u002F**\u002Flayout.{js,jsx,ts,tsx}",{"type":61,"value":1457}," — the read might live in any component imported by a layout). Unblock each match with the ",{"type":56,"tag":80,"props":1459,"children":1461},{"className":1460},[],[1462],{"type":61,"value":1463},"await connection()",{"type":61,"value":1465}," + ",{"type":56,"tag":80,"props":1467,"children":1469},{"className":1468},[],[1470],{"type":61,"value":100},{"type":61,"value":1472}," fix from its ",{"type":56,"tag":80,"props":1474,"children":1476},{"className":1475},[],[1477],{"type":61,"value":1478},"blocking-prerender-*",{"type":61,"value":1480}," error card: it defers the value to request time, exactly as it behaved before the migration, so it needs no product decision. Add this exact comment on the line above the ",{"type":56,"tag":80,"props":1482,"children":1484},{"className":1483},[],[1485],{"type":61,"value":1463},{"type":61,"value":1487},":",{"type":56,"tag":1135,"props":1489,"children":1493},{"className":1490,"code":1491,"language":1492,"meta":1140,"style":1140},"language-tsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F TODO: Cache Components adoption. Added to unblock the build: remove this connection() to re-trigger the error and review the fix options.\n","tsx",[1494],{"type":56,"tag":80,"props":1495,"children":1496},{"__ignoreMap":1140},[1497],{"type":56,"tag":1146,"props":1498,"children":1499},{"class":1148,"line":1149},[1500],{"type":56,"tag":1146,"props":1501,"children":1503},{"style":1502},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[1504],{"type":61,"value":1491},{"type":56,"tag":1122,"props":1506,"children":1507},{},[],{"type":61,"value":1509},"It shares the ",{"type":56,"tag":80,"props":1511,"children":1513},{"className":1512},[],[1514],{"type":61,"value":1515},"TODO: Cache Components adoption",{"type":61,"value":1517}," prefix with the comments the codemod writes, so the check-in grep finds both. Removing the ",{"type":56,"tag":80,"props":1519,"children":1521},{"className":1520},[],[1522],{"type":61,"value":1463},{"type":61,"value":1524}," makes the error fire again with its fix cards — the same motion as removing an opt-out in the loop.",{"type":56,"tag":114,"props":1526,"children":1527},{},[1528,1533,1535,1541,1543,1548,1550,1555],{"type":56,"tag":118,"props":1529,"children":1530},{},[1531],{"type":61,"value":1532},"Incompatible segment configs.",{"type":61,"value":1534}," Grep for ",{"type":56,"tag":80,"props":1536,"children":1538},{"className":1537},[],[1539],{"type":61,"value":1540},"^export const (revalidate|dynamic|fetchCache)",{"type":61,"value":1542}," across ",{"type":56,"tag":80,"props":1544,"children":1546},{"className":1545},[],[1547],{"type":61,"value":161},{"type":61,"value":1549}," and translate per the ",{"type":56,"tag":80,"props":1551,"children":1553},{"className":1552},[],[1554],{"type":61,"value":106},{"type":61,"value":1556}," note above. The codemod does not touch them; leaving them in place fails the build after the codemod.",{"type":56,"tag":63,"props":1558,"children":1559},{},[1560,1562,1568,1570,1575],{"type":61,"value":1561},"The codemod refuses to run on a dirty working tree. Commit or stash unrelated work first, or pass ",{"type":56,"tag":80,"props":1563,"children":1565},{"className":1564},[],[1566],{"type":61,"value":1567},"--force",{"type":61,"value":1569}," to let its edits land alongside your WIP. Common false positive: if you recently upgraded Next.js, ",{"type":56,"tag":80,"props":1571,"children":1573},{"className":1572},[],[1574],{"type":61,"value":1200},{"type":61,"value":1576}," and the lockfile will already be dirty — commit those first.",{"type":56,"tag":63,"props":1578,"children":1579},{},[1580,1582,1588,1590,1596,1598,1603,1605,1611,1613,1619,1621,1627],{"type":61,"value":1581},"Use the ",{"type":56,"tag":80,"props":1583,"children":1585},{"className":1584},[],[1586],{"type":61,"value":1587},"@canary",{"type":61,"value":1589}," channel, not ",{"type":56,"tag":80,"props":1591,"children":1593},{"className":1592},[],[1594],{"type":61,"value":1595},"@latest",{"type":61,"value":1597},". The ",{"type":56,"tag":80,"props":1599,"children":1601},{"className":1600},[],[1602],{"type":61,"value":263},{"type":61,"value":1604}," transform isn't in the stable ",{"type":56,"tag":80,"props":1606,"children":1608},{"className":1607},[],[1609],{"type":61,"value":1610},"@next\u002Fcodemod",{"type":61,"value":1612}," release; ",{"type":56,"tag":80,"props":1614,"children":1616},{"className":1615},[],[1617],{"type":61,"value":1618},"@next\u002Fcodemod@latest",{"type":61,"value":1620}," errors with ",{"type":56,"tag":80,"props":1622,"children":1624},{"className":1623},[],[1625],{"type":61,"value":1626},"Invalid transform choice",{"type":61,"value":1628},".",{"type":56,"tag":1135,"props":1630,"children":1632},{"className":1137,"code":1631,"language":1139,"meta":1140,"style":1140},"npx @next\u002Fcodemod@canary cache-components-instant-false .\u002Fapp\n",[1633],{"type":56,"tag":80,"props":1634,"children":1635},{"__ignoreMap":1140},[1636],{"type":56,"tag":1146,"props":1637,"children":1638},{"class":1148,"line":1149},[1639,1643,1648,1653,1659],{"type":56,"tag":1146,"props":1640,"children":1641},{"style":1153},[1642],{"type":61,"value":1156},{"type":56,"tag":1146,"props":1644,"children":1645},{"style":1159},[1646],{"type":61,"value":1647}," @next\u002Fcodemod@canary",{"type":56,"tag":1146,"props":1649,"children":1650},{"style":1159},[1651],{"type":61,"value":1652}," cache-components-instant-",{"type":56,"tag":1146,"props":1654,"children":1656},{"style":1655},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[1657],{"type":61,"value":1658},"false",{"type":56,"tag":1146,"props":1660,"children":1661},{"style":1159},[1662],{"type":61,"value":1663}," .\u002Fapp\n",{"type":56,"tag":63,"props":1665,"children":1666},{},[1667,1669,1674,1676,1681,1683,1689,1691,1696,1698,1704,1705,1711,1713,1718],{"type":61,"value":1668},"Inserts ",{"type":56,"tag":80,"props":1670,"children":1672},{"className":1671},[],[1673],{"type":61,"value":702},{"type":61,"value":1675}," (with a ",{"type":56,"tag":80,"props":1677,"children":1679},{"className":1678},[],[1680],{"type":61,"value":1336},{"type":61,"value":1682}," comment) into every ",{"type":56,"tag":80,"props":1684,"children":1686},{"className":1685},[],[1687],{"type":61,"value":1688},"app\u002F**\u002F{page,layout,default}",{"type":61,"value":1690}," file, skipping files that already declare ",{"type":56,"tag":80,"props":1692,"children":1694},{"className":1693},[],[1695],{"type":61,"value":889},{"type":61,"value":1697}," and any module marked ",{"type":56,"tag":80,"props":1699,"children":1701},{"className":1700},[],[1702],{"type":61,"value":1703},"\"use client\"",{"type":61,"value":147},{"type":56,"tag":80,"props":1706,"children":1708},{"className":1707},[],[1709],{"type":61,"value":1710},"\"use server\"",{"type":61,"value":1712},". Then set ",{"type":56,"tag":80,"props":1714,"children":1716},{"className":1715},[],[1717],{"type":61,"value":130},{"type":61,"value":1719},". The TODO comments are the work queue for the loop.",{"type":56,"tag":63,"props":1721,"children":1722},{},[1723,1725,1730,1732,1738,1740,1745,1746,1751,1753,1758],{"type":61,"value":1724},"If the codemod isn't available (older ",{"type":56,"tag":80,"props":1726,"children":1728},{"className":1727},[],[1729],{"type":61,"value":1610},{"type":61,"value":1731},", sandboxed environment, offline run), reproduce it by hand: for every ",{"type":56,"tag":80,"props":1733,"children":1735},{"className":1734},[],[1736],{"type":61,"value":1737},"app\u002F**\u002F{page,layout,default}.{js,jsx,ts,tsx}",{"type":61,"value":1739}," that isn't ",{"type":56,"tag":80,"props":1741,"children":1743},{"className":1742},[],[1744],{"type":61,"value":1703},{"type":61,"value":147},{"type":56,"tag":80,"props":1747,"children":1749},{"className":1748},[],[1750],{"type":61,"value":1710},{"type":61,"value":1752}," and doesn't already declare ",{"type":56,"tag":80,"props":1754,"children":1756},{"className":1755},[],[1757],{"type":61,"value":889},{"type":61,"value":1759},", insert this after the imports:",{"type":56,"tag":1135,"props":1761,"children":1765},{"className":1762,"code":1763,"language":1764,"meta":1140,"style":1140},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.\n\u002F\u002F See: https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fmigrating-to-cache-components\nexport const instant = false\n","ts",[1766],{"type":56,"tag":80,"props":1767,"children":1768},{"__ignoreMap":1140},[1769,1777,1786],{"type":56,"tag":1146,"props":1770,"children":1771},{"class":1148,"line":1149},[1772],{"type":56,"tag":1146,"props":1773,"children":1774},{"style":1502},[1775],{"type":61,"value":1776},"\u002F\u002F TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.\n",{"type":56,"tag":1146,"props":1778,"children":1780},{"class":1148,"line":1779},2,[1781],{"type":56,"tag":1146,"props":1782,"children":1783},{"style":1502},[1784],{"type":61,"value":1785},"\u002F\u002F See: https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fmigrating-to-cache-components\n",{"type":56,"tag":1146,"props":1787,"children":1789},{"class":1148,"line":1788},3,[1790,1796,1802,1808,1813],{"type":56,"tag":1146,"props":1791,"children":1793},{"style":1792},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[1794],{"type":61,"value":1795},"export",{"type":56,"tag":1146,"props":1797,"children":1799},{"style":1798},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[1800],{"type":61,"value":1801}," const",{"type":56,"tag":1146,"props":1803,"children":1805},{"style":1804},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[1806],{"type":61,"value":1807}," instant ",{"type":56,"tag":1146,"props":1809,"children":1810},{"style":1655},[1811],{"type":61,"value":1812},"=",{"type":56,"tag":1146,"props":1814,"children":1816},{"style":1815},"--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC",[1817],{"type":61,"value":1818}," false\n",{"type":56,"tag":63,"props":1820,"children":1821},{},[1822,1824,1829],{"type":61,"value":1823},"The codemod opts every segment out, not only the root, on purpose. Resolution is top-down, first-explicit-config-wins: the highest ",{"type":56,"tag":80,"props":1825,"children":1827},{"className":1826},[],[1828],{"type":61,"value":851},{"type":61,"value":1830}," decides the whole subtree. With an opt-out on every segment, removing one segment's opt-out validates only that segment; descendants keep their own opt-outs and stay passing. If only the root were opted out, removing it would re-arm validation for the entire app at once.",{"type":56,"tag":63,"props":1832,"children":1833},{},[1834],{"type":61,"value":1835},"Because the highest opt-out wins, remove them top-down (root layout first, then descend). Removing a leaf's opt-out does nothing while an ancestor still holds one.",{"type":56,"tag":63,"props":1837,"children":1838},{},[1839,1841,1846,1848,1853,1854,1859,1861,1866],{"type":61,"value":1840},"Confirm the pre-step with ",{"type":56,"tag":80,"props":1842,"children":1844},{"className":1843},[],[1845],{"type":61,"value":670},{"type":61,"value":1847},". The build is the proof, not the codemod run — a shared layout that calls ",{"type":56,"tag":80,"props":1849,"children":1851},{"className":1850},[],[1852],{"type":61,"value":822},{"type":61,"value":573},{"type":56,"tag":80,"props":1855,"children":1857},{"className":1856},[],[1858],{"type":61,"value":836},{"type":61,"value":1860}," directly still fails regardless of the opt-out (see ",{"type":56,"tag":69,"props":1862,"children":1864},{"href":1863},"#background",[1865],{"type":61,"value":677},{"type":61,"value":1867},").",{"type":56,"tag":63,"props":1869,"children":1870},{},[1871,1873,1879,1881,1887,1889,1894],{"type":61,"value":1872},"After the build passes, confirm the root layout got an opt-out (",{"type":56,"tag":80,"props":1874,"children":1876},{"className":1875},[],[1877],{"type":61,"value":1878},"grep -n \"export const instant\" app\u002Flayout.*",{"type":61,"value":1880},"). The root layout renders every route, including framework routes like ",{"type":56,"tag":80,"props":1882,"children":1884},{"className":1883},[],[1885],{"type":61,"value":1886},"\u002F_not-found",{"type":61,"value":1888},", so if it was missed, add ",{"type":56,"tag":80,"props":1890,"children":1892},{"className":1891},[],[1893],{"type":61,"value":702},{"type":61,"value":1895}," to it by hand.",{"type":56,"tag":63,"props":1897,"children":1898},{},[1899,1901,1906,1908,1913,1915,1921,1923,1928,1930,1936,1937,1943,1945,1950,1952,1958,1960,1967,1969,1974,1976,1981],{"type":61,"value":1900},"Synthetic routes like ",{"type":56,"tag":80,"props":1902,"children":1904},{"className":1903},[],[1905],{"type":61,"value":1886},{"type":61,"value":1907}," have no user file — when they block, fix the root layout's opt-out, not the synthetic route. Client Components (",{"type":56,"tag":80,"props":1909,"children":1911},{"className":1910},[],[1912],{"type":61,"value":1703},{"type":61,"value":1914},") get no opt-out (it's a build error — ",{"type":56,"tag":80,"props":1916,"children":1918},{"className":1917},[],[1919],{"type":61,"value":1920},"E1344",{"type":61,"value":1922}," — to export ",{"type":56,"tag":80,"props":1924,"children":1926},{"className":1925},[],[1927],{"type":61,"value":889},{"type":61,"value":1929}," from them), but they are not a rare blocker. The high-frequency case is a client component in the root layout's nav or header calling ",{"type":56,"tag":80,"props":1931,"children":1933},{"className":1932},[],[1934],{"type":61,"value":1935},"usePathname()",{"type":61,"value":624},{"type":56,"tag":80,"props":1938,"children":1940},{"className":1939},[],[1941],{"type":61,"value":1942},"useSearchParams()",{"type":61,"value":1944},": it blocks ",{"type":56,"tag":491,"props":1946,"children":1947},{},[1948],{"type":61,"value":1949},"every",{"type":61,"value":1951}," dynamic route with ",{"type":56,"tag":80,"props":1953,"children":1955},{"className":1954},[],[1956],{"type":61,"value":1957},"blocking-prerender-client-hook",{"type":61,"value":1959},", and static routes pass (the pathname is known at prerender), which masks it until you reach a dynamic segment. It's not an ancestor-data fix — follow the ",{"type":56,"tag":69,"props":1961,"children":1964},{"href":1962,"rel":1963},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fmessages\u002Fblocking-prerender-client-hook",[73],[1965],{"type":61,"value":1966},"error's docs page",{"type":61,"value":1968}," for the ",{"type":56,"tag":80,"props":1970,"children":1972},{"className":1971},[],[1973],{"type":61,"value":100},{"type":61,"value":1975}," recipe. Only when a client route blocks on ",{"type":56,"tag":491,"props":1977,"children":1978},{},[1979],{"type":61,"value":1980},"server",{"type":61,"value":1982}," data do you fix that data in its ancestor.",{"type":56,"tag":457,"props":1984,"children":1986},{"id":1985},"end-of-the-pre-step-check-in",[1987],{"type":61,"value":1988},"end of the pre-step: check in",{"type":56,"tag":63,"props":1990,"children":1991},{},[1992],{"type":61,"value":1993},"Incremental only. Stop here before starting step 2 — the pre-step is the shippable PR. Talk to the user in their language; don't say \"Incremental\" or other internal labels; talk about adoption, PRs, and what the app does now. Tell them:",{"type":56,"tag":110,"props":1995,"children":1996},{},[1997,2002,2035,2040],{"type":56,"tag":114,"props":1998,"children":1999},{},[2000],{"type":61,"value":2001},"What you did: turned on Cache Components, ran the codemod that opts every page and layout out of the new validation (or did it by hand), fixed any blockers the codemod can't (list them), confirmed the build passes.",{"type":56,"tag":114,"props":2003,"children":2004},{},[2005,2007,2012,2014,2019,2021,2026,2028,2033],{"type":61,"value":2006},"What changed: every page and layout in ",{"type":56,"tag":80,"props":2008,"children":2010},{"className":2009},[],[2011],{"type":61,"value":161},{"type":61,"value":2013}," now exports ",{"type":56,"tag":80,"props":2015,"children":2017},{"className":2016},[],[2018],{"type":61,"value":851},{"type":61,"value":2020}," with a ",{"type":56,"tag":80,"props":2022,"children":2024},{"className":2023},[],[2025],{"type":61,"value":1336},{"type":61,"value":2027}," comment, except client components and any that already had an ",{"type":56,"tag":80,"props":2029,"children":2031},{"className":2030},[],[2032],{"type":61,"value":889},{"type":61,"value":2034}," export.",{"type":56,"tag":114,"props":2036,"children":2037},{},[2038],{"type":61,"value":2039},"What to sanity-check: the diff is mostly mechanical (new exports + comments). The build passes. Routes still behave exactly as they did before — the opt-outs preserve current behavior; no rendering changes yet.",{"type":56,"tag":114,"props":2041,"children":2042},{},[2043],{"type":61,"value":2044},"The question: \"Want to open this as its own PR before we start adopting Cache Components route by route? Or keep going on this branch?\" Wait for the answer.",{"type":56,"tag":63,"props":2046,"children":2047},{},[2048],{"type":61,"value":2049},"Moving to step 2 without checking in defeats the point of taking the incremental path.",{"type":56,"tag":457,"props":2051,"children":2053},{"id":2052},"direct",[2054],{"type":61,"value":2052},{"type":56,"tag":63,"props":2056,"children":2057},{},[2058,2060,2065,2067,2071],{"type":61,"value":2059},"Set ",{"type":56,"tag":80,"props":2061,"children":2063},{"className":2062},[],[2064],{"type":61,"value":130},{"type":61,"value":2066}," and move to ",{"type":56,"tag":69,"props":2068,"children":2069},{"href":957},[2070],{"type":61,"value":960},{"type":61,"value":2072},". The build's blocking routes are the work queue.",{"type":56,"tag":104,"props":2074,"children":2076},{"id":2075},"step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time",[2077],{"type":61,"value":2078},"step 2: the inner loop, remove opt-outs one feature at a time",{"type":56,"tag":63,"props":2080,"children":2081},{},[2082,2084,2090,2091,2097,2099,2105],{"type":61,"value":2083},"A \"feature\" is a single product surface — ",{"type":56,"tag":80,"props":2085,"children":2087},{"className":2086},[],[2088],{"type":61,"value":2089},"app\u002Fsettings\u002Fprofile\u002F**",{"type":61,"value":87},{"type":56,"tag":80,"props":2092,"children":2094},{"className":2093},[],[2095],{"type":61,"value":2096},"app\u002Fposts\u002F[slug]\u002F**",{"type":61,"value":2098}," — not a whole top-level app like ",{"type":56,"tag":80,"props":2100,"children":2102},{"className":2101},[],[2103],{"type":61,"value":2104},"app\u002Fdashboard\u002F**",{"type":61,"value":2106},". Finish one end-to-end before starting the next.",{"type":56,"tag":63,"props":2108,"children":2109},{},[2110],{"type":61,"value":2111},"Within a feature, walk top-down (layouts before pages, root layout first). Removing a layout's opt-out before its descendants exposes the layout's own blocking reads. (Direct: there are no opt-outs to remove — fix each failing route; if a hand-written opt-out on an ancestor shadows it, remove that first.)",{"type":56,"tag":63,"props":2113,"children":2114},{},[2115],{"type":61,"value":2116},"A passing build mid-walk doesn't mean the layout is clean. Removing a layout's opt-out while its descendant pages still have theirs keeps the build passing — each page shadows the inherited validation. The layout's actual blocking reads only surface once nothing below it shadows them. Don't call a feature done at the layout boundary.",{"type":56,"tag":63,"props":2118,"children":2119},{},[2120,2121,2126,2128,2137],{"type":61,"value":1581},{"type":56,"tag":118,"props":2122,"children":2123},{},[2124],{"type":61,"value":2125},"with-a-browser",{"type":61,"value":2127}," loop unless a browser is genuinely unreachable. The ",{"type":56,"tag":69,"props":2129,"children":2131},{"href":2130},"#verifying-each-fix-at-runtime",[2132],{"type":56,"tag":80,"props":2133,"children":2135},{"className":2134},[],[2136],{"type":61,"value":1093},{"type":61,"value":2138}," skill is the source of truth for what counts as \"browser available\" and how to install it.",{"type":56,"tag":457,"props":2140,"children":2142},{"id":2141},"the-loop-with-a-browser-preferred",[2143],{"type":61,"value":2144},"the loop, with a browser (preferred)",{"type":56,"tag":63,"props":2146,"children":2147},{},[2148],{"type":61,"value":2149},"Per route:",{"type":56,"tag":110,"props":2151,"children":2152},{},[2153,2158,2163,2175,2180],{"type":56,"tag":114,"props":2154,"children":2155},{},[2156],{"type":61,"value":2157},"Remove the opt-out (Incremental) or target the failing route (Direct).",{"type":56,"tag":114,"props":2159,"children":2160},{},[2161],{"type":61,"value":2162},"Reload in dev. Overlay clean? Skip to verify. Overlay still red? Fix.",{"type":56,"tag":114,"props":2164,"children":2165},{},[2166,2168,2173],{"type":61,"value":2167},"Fix — fetch the docs page linked from the error (",{"type":56,"tag":80,"props":2169,"children":2171},{"className":2170},[],[2172],{"type":61,"value":1041},{"type":61,"value":2174},"), apply the recipe from there. The inline overlay text is a summary; the docs page is the source of truth.",{"type":56,"tag":114,"props":2176,"children":2177},{},[2178],{"type":61,"value":2179},"Verify in the browser. Confirm the visible content on first paint is what you intended in the shell — not stuck on a fallback, not silently streaming everything out of an empty shell.",{"type":56,"tag":114,"props":2181,"children":2182},{},[2183],{"type":61,"value":2184},"Re-check siblings if the fix touched shared code (a layout, a sidebar component). A shared-shell change can fix the route you're on and break a sibling.",{"type":56,"tag":457,"props":2186,"children":2188},{"id":2187},"the-loop-build-only-fallback",[2189],{"type":61,"value":2190},"the loop, build-only (fallback)",{"type":56,"tag":63,"props":2192,"children":2193},{},[2194,2196,2201],{"type":61,"value":2195},"Used when there's no way to drive a browser — CI, sandbox, the user has no ",{"type":56,"tag":80,"props":2197,"children":2199},{"className":2198},[],[2200],{"type":61,"value":214},{"type":61,"value":2202}," running and you can't start one. Weaker signal: confirms the build passes and the route prerenders, but not what ended up in the static shell vs streamed.",{"type":56,"tag":63,"props":2204,"children":2205},{},[2206],{"type":61,"value":2149},{"type":56,"tag":110,"props":2208,"children":2209},{},[2210,2214,2234,2245,2250],{"type":56,"tag":114,"props":2211,"children":2212},{},[2213],{"type":61,"value":2157},{"type":56,"tag":114,"props":2215,"children":2216},{},[2217,2219,2225,2227,2232],{"type":61,"value":2218},"Rebuild with ",{"type":56,"tag":80,"props":2220,"children":2222},{"className":2221},[],[2223],{"type":61,"value":2224},"--debug-build-paths app\u002F\u003Croute>\u002F**",{"type":61,"value":2226}," (only that route) or ",{"type":56,"tag":80,"props":2228,"children":2230},{"className":2229},[],[2231],{"type":61,"value":1028},{"type":61,"value":2233}," (full build, but past the first failure). Route passing? Move on. Still blocking? Fix.",{"type":56,"tag":114,"props":2235,"children":2236},{},[2237,2238,2243],{"type":61,"value":2167},{"type":56,"tag":80,"props":2239,"children":2241},{"className":2240},[],[2242],{"type":61,"value":1041},{"type":61,"value":2244},"), apply the recipe from there.",{"type":56,"tag":114,"props":2246,"children":2247},{},[2248],{"type":61,"value":2249},"Re-check siblings if the fix touched shared code.",{"type":56,"tag":114,"props":2251,"children":2252},{},[2253,2255,2261],{"type":61,"value":2254},"Flag the route as build-only-verified when you hand the feature off. Each ",{"type":56,"tag":80,"props":2256,"children":2258},{"className":2257},[],[2259],{"type":61,"value":2260},"◐",{"type":61,"value":2262}," route still needs a browser pass before the feature is done.",{"type":56,"tag":457,"props":2264,"children":2266},{"id":2265},"loop-notes",[2267],{"type":61,"value":2268},"loop notes",{"type":56,"tag":110,"props":2270,"children":2271},{},[2272,2306,2326],{"type":56,"tag":114,"props":2273,"children":2274},{},[2275,2277,2282,2284,2290,2292,2297,2299,2304],{"type":61,"value":2276},"The ",{"type":56,"tag":69,"props":2278,"children":2279},{"href":1863},[2280],{"type":61,"value":2281},"three blocker classes from background",{"type":61,"value":2283}," often get missed when fixing in place. Caching a downstream fetch (",{"type":56,"tag":80,"props":2285,"children":2287},{"className":2286},[],[2288],{"type":61,"value":2289},"getThing(id)",{"type":61,"value":2291},") doesn't clear an ",{"type":56,"tag":80,"props":2293,"children":2295},{"className":2294},[],[2296],{"type":61,"value":755},{"type":61,"value":2298}," at the top of the page body — push the param promise into the ",{"type":56,"tag":80,"props":2300,"children":2302},{"className":2301},[],[2303],{"type":61,"value":100},{"type":61,"value":2305},"-wrapped child.",{"type":56,"tag":114,"props":2307,"children":2308},{},[2309,2311,2317,2319,2324],{"type":61,"value":2310},"Ambiguous calls are user check-ins, not agent judgment. When you're not sure which fix fits, the blocking code looks security-sensitive, or the user might want to keep the route blocking on purpose — read ",{"type":56,"tag":69,"props":2312,"children":2314},{"href":2313},".\u002Freferences\u002Fper-page-decisions.md",[2315],{"type":61,"value":2316},"references\u002Fper-page-decisions.md",{"type":61,"value":2318}," before editing. Show the route while you ask: the ",{"type":56,"tag":80,"props":2320,"children":2322},{"className":2321},[],[2323],{"type":61,"value":1093},{"type":61,"value":2325}," session runs the browser headed, so drive to the page and leave it on screen so the user is looking at the thing they're deciding about, with a screenshot as the fallback when a headed browser isn't possible. \"Should this stay blocking?\" is much easier to answer while looking at the page than at a file path.",{"type":56,"tag":114,"props":2327,"children":2328},{},[2329,2331,2336,2338,2343,2345,2350,2352,2357],{"type":61,"value":2330},"Don't narrate the refactor with comments. The only comment the codemod (or you) should leave is ",{"type":56,"tag":80,"props":2332,"children":2334},{"className":2333},[],[2335],{"type":61,"value":1336},{"type":61,"value":2337}," on opt-outs, and the user's existing comments. Don't annotate every ",{"type":56,"tag":80,"props":2339,"children":2341},{"className":2340},[],[2342],{"type":61,"value":100},{"type":61,"value":2344}," boundary or ",{"type":56,"tag":80,"props":2346,"children":2348},{"className":2347},[],[2349],{"type":61,"value":85},{"type":61,"value":2351}," call with what it does — the code says that. Drop a comment only when the ",{"type":56,"tag":491,"props":2353,"children":2354},{},[2355],{"type":61,"value":2356},"why",{"type":61,"value":2358}," isn't clear from the code (e.g. a deliberate Block with a reason).",{"type":56,"tag":63,"props":2360,"children":2361},{},[2362],{"type":61,"value":2363},"Keep a todo list of the feature's routes. When every route in the feature is clean, move to step 3.",{"type":56,"tag":104,"props":2365,"children":2367},{"id":2366},"step-3-verify-the-feature",[2368],{"type":61,"value":2369},"step 3: verify the feature",{"type":56,"tag":63,"props":2371,"children":2372},{},[2373],{"type":61,"value":2374},"Checklist before checking in with the user:",{"type":56,"tag":110,"props":2376,"children":2377},{},[2378,2388,2421],{"type":56,"tag":114,"props":2379,"children":2380},{},[2381,2386],{"type":56,"tag":80,"props":2382,"children":2384},{"className":2383},[],[2385],{"type":61,"value":670},{"type":61,"value":2387}," completes without blocking-route errors.",{"type":56,"tag":114,"props":2389,"children":2390},{},[2391,2393,2399,2401,2406,2408,2412,2414,2419],{"type":61,"value":2392},"No bare TODOs in the feature: ",{"type":56,"tag":80,"props":2394,"children":2396},{"className":2395},[],[2397],{"type":61,"value":2398},"grep -rn \"TODO: Cache Components adoption\"",{"type":61,"value":2400}," finds both the codemod's opt-out comments and the sync-IO unblocks from the pre-step. Any ",{"type":56,"tag":80,"props":2402,"children":2404},{"className":2403},[],[2405],{"type":61,"value":851},{"type":61,"value":2407}," left behind is a deliberate, documented Block — comment rewritten to a reason (see ",{"type":56,"tag":69,"props":2409,"children":2410},{"href":2313},[2411],{"type":61,"value":2316},{"type":61,"value":2413}," → \"when to leave a Block in place\"). Any ",{"type":56,"tag":80,"props":2415,"children":2417},{"className":2416},[],[2418],{"type":61,"value":1463},{"type":61,"value":2420}," left behind has been reviewed and kept on purpose, not left over from the pre-step.",{"type":56,"tag":114,"props":2422,"children":2423},{},[2424,2426,2431],{"type":61,"value":2425},"Each route visited in the browser: confirm the static shell renders first and every ",{"type":56,"tag":80,"props":2427,"children":2429},{"className":2428},[],[2430],{"type":61,"value":100},{"type":61,"value":2432}," fallback resolves to its real content. Capture both states if you can — the fallback (mid-stream) and the final paint — so you have a streaming-experience demo to show the user. Throttle the network in the browser if streaming is too fast to observe.",{"type":56,"tag":63,"props":2434,"children":2435},{},[2436],{"type":61,"value":2437},"Then check in with the user. Same rule as the pre-step: speak their language. Don't say \"feature-by-feature loop\" or other internal labels; talk about the feature you adopted and what the user will see.",{"type":56,"tag":110,"props":2439,"children":2440},{},[2441,2446,2451,2463,2468],{"type":56,"tag":114,"props":2442,"children":2443},{},[2444],{"type":61,"value":2445},"What you did: which routes you touched, and the user-visible result per route (e.g. \"the post page now streams the article body behind a skeleton while the layout stays static\").",{"type":56,"tag":114,"props":2447,"children":2448},{},[2449],{"type":61,"value":2450},"What changed: opt-outs removed, fallbacks added, caching boundaries introduced.",{"type":56,"tag":114,"props":2452,"children":2453},{},[2454,2456,2461],{"type":61,"value":2455},"Show, don't tell. The ",{"type":56,"tag":80,"props":2457,"children":2459},{"className":2458},[],[2460],{"type":61,"value":1093},{"type":61,"value":2462}," session runs the browser headed, so drive the route live for the user so they see the static shell → fallback → final content sequence in real time. If you can't drive a live browser, attach the before\u002Fafter screenshots you captured instead.",{"type":56,"tag":114,"props":2464,"children":2465},{},[2466],{"type":61,"value":2467},"Give them the click-through: a short table of the feature's routes — the URL to open and what to look for (what renders instantly, which fallbacks appear, what streams in) — so they can verify each one themselves.",{"type":56,"tag":114,"props":2469,"children":2470},{},[2471],{"type":61,"value":2472},"The question: \"Want to open this feature as a PR and move on to the next, or stop here?\" Wait for the answer.",{"type":56,"tag":63,"props":2474,"children":2475},{},[2476,2481,2483,2488,2490,2495,2497,2503],{"type":56,"tag":118,"props":2477,"children":2478},{},[2479],{"type":61,"value":2480},"Trivial features can skip the check-in.",{"type":61,"value":2482}," If adopting a feature only meant removing its ",{"type":56,"tag":80,"props":2484,"children":2486},{"className":2485},[],[2487],{"type":61,"value":1336},{"type":61,"value":2489}," opt-out (no ",{"type":56,"tag":80,"props":2491,"children":2493},{"className":2492},[],[2494],{"type":61,"value":100},{"type":61,"value":2496}," added, no ",{"type":56,"tag":80,"props":2498,"children":2500},{"className":2499},[],[2501],{"type":61,"value":2502},"'use cache'",{"type":61,"value":2504}," introduced, no render order change), the user sees nothing different. Move on to the next feature without stopping; mention it in passing the next time you do check in.",{"type":56,"tag":63,"props":2506,"children":2507},{},[2508,2510,2515,2517,2523,2525,2531],{"type":61,"value":2509},"When the loop has run on every feature — every remaining ",{"type":56,"tag":80,"props":2511,"children":2513},{"className":2512},[],[2514],{"type":61,"value":851},{"type":61,"value":2516}," sits under a reason comment, ",{"type":56,"tag":80,"props":2518,"children":2520},{"className":2519},[],[2521],{"type":61,"value":2522},"grep -rln \"TODO: Cache Components adoption\" app",{"type":61,"value":2524}," returns nothing — point the user at ",{"type":56,"tag":69,"props":2526,"children":2528},{"href":2527},"#further-reading",[2529],{"type":61,"value":2530},"further reading",{"type":61,"value":2532}," if they want to push the experience further, or stop and ship.",{"type":56,"tag":457,"props":2534,"children":2536},{"id":2535},"route-table-glyphs",[2537],{"type":61,"value":2538},"route table glyphs",{"type":56,"tag":63,"props":2540,"children":2541},{},[2542,2548,2550,2555,2557,2562,2564,2569,2570,2575,2576,2581,2582,2587,2589,2594,2596,2601,2603,2608,2610,2615,2617,2622,2624,2629],{"type":56,"tag":80,"props":2543,"children":2545},{"className":2544},[],[2546],{"type":61,"value":2547},"ƒ",{"type":61,"value":2549}," → ",{"type":56,"tag":80,"props":2551,"children":2553},{"className":2552},[],[2554],{"type":61,"value":2260},{"type":61,"value":2556}," is where adoption usually lands. ",{"type":56,"tag":80,"props":2558,"children":2560},{"className":2559},[],[2561],{"type":61,"value":1300},{"type":61,"value":2563}," means a static shell prerenders and the request-time content streams in — the goal state for any route that reads ",{"type":56,"tag":80,"props":2565,"children":2567},{"className":2566},[],[2568],{"type":61,"value":741},{"type":61,"value":87},{"type":56,"tag":80,"props":2571,"children":2573},{"className":2572},[],[2574],{"type":61,"value":748},{"type":61,"value":87},{"type":56,"tag":80,"props":2577,"children":2579},{"className":2578},[],[2580],{"type":61,"value":770},{"type":61,"value":342},{"type":56,"tag":80,"props":2583,"children":2585},{"className":2584},[],[2586],{"type":61,"value":777},{"type":61,"value":2588},". Some routes legitimately stay ",{"type":56,"tag":80,"props":2590,"children":2592},{"className":2591},[],[2593],{"type":61,"value":2547},{"type":61,"value":2595}," when they do request-time work through a documented escape hatch (e.g. a layout that uses ",{"type":56,"tag":80,"props":2597,"children":2599},{"className":2598},[],[2600],{"type":61,"value":1463},{"type":61,"value":2602},"); the page is no longer ",{"type":56,"tag":491,"props":2604,"children":2605},{},[2606],{"type":61,"value":2607},"opted out",{"type":61,"value":2609},", it's genuinely dynamic. Don't remove the escape hatch only to chase a ",{"type":56,"tag":80,"props":2611,"children":2613},{"className":2612},[],[2614],{"type":61,"value":2260},{"type":61,"value":2616},". The inverse holds: ",{"type":56,"tag":80,"props":2618,"children":2620},{"className":2619},[],[2621],{"type":61,"value":851},{"type":61,"value":2623}," does not force a route to be ",{"type":56,"tag":80,"props":2625,"children":2627},{"className":2626},[],[2628],{"type":61,"value":2547},{"type":61,"value":2630},". The glyph reflects what the route does at prerender time, not which validation knobs it exports.",{"type":56,"tag":63,"props":2632,"children":2633},{},[2634,2639,2641,2646,2648,2654,2656,2661,2663,2668,2670,2676,2678,2683],{"type":56,"tag":80,"props":2635,"children":2637},{"className":2636},[],[2638],{"type":61,"value":2260},{"type":61,"value":2640}," tells you a shell exists, not what's in it. A ",{"type":56,"tag":80,"props":2642,"children":2644},{"className":2643},[],[2645],{"type":61,"value":100},{"type":61,"value":2647}," boundary placed too high (e.g. wrapping the entire page body, or ",{"type":56,"tag":80,"props":2649,"children":2651},{"className":2650},[],[2652],{"type":61,"value":2653},"\u003CSuspense fallback={null}>",{"type":61,"value":2655}," around the article content) pushes the visible content out of the static shell into the streamed payload; the build still reports ",{"type":56,"tag":80,"props":2657,"children":2659},{"className":2658},[],[2660],{"type":61,"value":2260},{"type":61,"value":2662}," because ",{"type":56,"tag":491,"props":2664,"children":2665},{},[2666],{"type":61,"value":2667},"some",{"type":61,"value":2669}," shell prerendered (often only ",{"type":56,"tag":80,"props":2671,"children":2673},{"className":2672},[],[2674],{"type":61,"value":2675},"\u003Chtml>\u003Cbody>",{"type":61,"value":2677}," with framework markup). The route table can't tell you what's in the shell; a browser can. If the shell is empty and everything streams, pull the ",{"type":56,"tag":80,"props":2679,"children":2681},{"className":2680},[],[2682],{"type":61,"value":100},{"type":61,"value":2684}," boundary down closer to the actual dynamic read.",{"type":56,"tag":104,"props":2686,"children":2688},{"id":2687},"further-reading",[2689],{"type":61,"value":2530},{"type":56,"tag":63,"props":2691,"children":2692},{},[2693],{"type":61,"value":2694},"The work below is optional and lives in the docs — link the user to them and let them decide which to take on next. Don't walk these through inside this skill.",{"type":56,"tag":110,"props":2696,"children":2697},{},[2698,2709,2757,2810],{"type":56,"tag":114,"props":2699,"children":2700},{},[2701,2707],{"type":56,"tag":69,"props":2702,"children":2704},{"href":2703},".\u002Freferences\u002Fdev-only-validations.md",[2705],{"type":61,"value":2706},"Sweep for more instant navigations",{"type":61,"value":2708}," — an optional follow-up once adoption is done, never required. A passing build is not the last word, because dev validates every route on each page load (simulating both page loads and client navigations) and catches what the build's first-error exit and descendant shadowing skipped. Offer it as the smaller path to instant navigation for a user who doesn't want to adopt Partial Prefetching. Adopting Partial Prefetching (below) runs the same kind of loop and meets these insights anyway, so recommend both and let the user pick which, or whether. The reference is the loop to execute.",{"type":56,"tag":114,"props":2710,"children":2711},{},[2712,2723,2725,2731,2733,2739,2741,2747,2749,2756],{"type":56,"tag":69,"props":2713,"children":2716},{"href":2714,"rel":2715},"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-partial-prefetching-adoption",[73],[2717],{"type":56,"tag":80,"props":2718,"children":2720},{"className":2719},[],[2721],{"type":61,"value":2722},"next-partial-prefetching-adoption",{"type":61,"value":2724}," — the follow-up skill that adopts Partial Prefetching: it enables ",{"type":56,"tag":80,"props":2726,"children":2728},{"className":2727},[],[2729],{"type":61,"value":2730},"partialPrefetching",{"type":61,"value":2732}," and audits every ",{"type":56,"tag":80,"props":2734,"children":2736},{"className":2735},[],[2737],{"type":61,"value":2738},"\u003CLink prefetch={true}>",{"type":61,"value":2740}," against a decision table (or adopts incrementally with the flag off, driven by the ",{"type":56,"tag":80,"props":2742,"children":2744},{"className":2743},[],[2745],{"type":61,"value":2746},"link-prefetch-partial",{"type":61,"value":2748}," insight). It sequences this the same way this skill sequences Cache Components, but the insights are dev-only, so it's a browser click-through, not a build loop. Recommended after instant navigation, since those fixes feed directly into how much of each route the shell can prefetch. Concepts live in the ",{"type":56,"tag":69,"props":2750,"children":2753},{"href":2751,"rel":2752},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Fadopting-partial-prefetching",[73],[2754],{"type":61,"value":2755},"Adopting Partial Prefetching guide",{"type":61,"value":1628},{"type":56,"tag":114,"props":2758,"children":2759},{},[2760,2767,2769,2775,2776,2787,2789,2794,2796,2801,2803,2808],{"type":56,"tag":69,"props":2761,"children":2764},{"href":2762,"rel":2763},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fguides\u002Finstant-navigation#prevent-regressions-with-e2e-tests",[73],[2765],{"type":61,"value":2766},"Prevent regressions with e2e tests",{"type":61,"value":2768}," — the ",{"type":56,"tag":80,"props":2770,"children":2772},{"className":2771},[],[2773],{"type":61,"value":2774},"@next\u002Fplaywright",{"type":61,"value":320},{"type":56,"tag":69,"props":2777,"children":2780},{"href":2778,"rel":2779},"https:\u002F\u002Fnextjs.org\u002Fdocs\u002Fapp\u002Fapi-reference\u002Ffile-conventions\u002Froute-segment-config\u002Finstant#testing-instant-navigation",[73],[2781],{"type":56,"tag":80,"props":2782,"children":2784},{"className":2783},[],[2785],{"type":61,"value":2786},"instant()",{"type":61,"value":2788}," helper asserts on the UI that's available immediately on navigation, so regressions surface in CI. Recommend it once a route is instant: ",{"type":56,"tag":80,"props":2790,"children":2792},{"className":2791},[],[2793],{"type":61,"value":1093},{"type":61,"value":2795}," confirms it ",{"type":56,"tag":491,"props":2797,"children":2798},{},[2799],{"type":61,"value":2800},"now",{"type":61,"value":2802},"; an ",{"type":56,"tag":80,"props":2804,"children":2806},{"className":2805},[],[2807],{"type":61,"value":2786},{"type":61,"value":2809}," test keeps it that way.",{"type":56,"tag":114,"props":2811,"children":2812},{},[2813,2824],{"type":56,"tag":69,"props":2814,"children":2817},{"href":2815,"rel":2816},"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fnext.js\u002Ftree\u002Fcanary\u002Fskills\u002Fnext-cache-components-optimizer",[73],[2818],{"type":56,"tag":80,"props":2819,"children":2821},{"className":2820},[],[2822],{"type":61,"value":2823},"next-cache-components-optimizer",{"type":61,"value":2825}," — a separate skill that grows each route's static shell so more of the page prerenders and less streams in. Pure optimization, not part of adoption.",{"type":56,"tag":2827,"props":2828,"children":2829},"style",{},[2830],{"type":61,"value":2831},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":2833,"total":2995},[2834,2842,2855,2873,2883,2898,2914,2932,2944,2963,2975,2985],{"slug":4,"name":4,"fn":5,"description":6,"org":2835,"tags":2836,"stars":26,"repoUrl":27,"updatedAt":28},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2837,2838,2839,2840,2841],{"name":21,"slug":22,"type":13},{"name":24,"slug":25,"type":13},{"name":18,"slug":19,"type":13},{"name":15,"slug":16,"type":13},{"name":9,"slug":8,"type":13},{"slug":2823,"name":2823,"fn":2843,"description":2844,"org":2845,"tags":2846,"stars":26,"repoUrl":27,"updatedAt":2854},"optimize Next.js cache components","Drive a Next.js route to instant navigation by setting up an agentic loop, under Cache Components \u002F PPR, on initial load (hard navigation) and client-side navigation (soft navigation). Encode the goal as a failing @next\u002Fplaywright instant() e2e and work it to green, one verified route at a time; the shipped test then guards against regression. Use when asked to make a route's navigation instant (its static shell commits immediately), fix a route whose static shell isn't prerendered\u002Fserved\u002Fprefetched, grow a route's static shell or fix its slow first paint, diagnose which Suspense boundary keeps a route out of its static shell, or write the instant() e2e guard for one. Requires Next.js 16.3+ with cacheComponents; directs an upgrade if older.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2847,2848,2849,2850,2853],{"name":21,"slug":22,"type":13},{"name":24,"slug":25,"type":13},{"name":15,"slug":16,"type":13},{"name":2851,"slug":2852,"type":13},"Performance","performance",{"name":9,"slug":8,"type":13},"2026-07-30T05:31:10.674078",{"slug":1093,"name":1093,"fn":2856,"description":2857,"org":2858,"tags":2859,"stars":26,"repoUrl":27,"updatedAt":2872},"verify Next.js runtime behavior","Verify Next.js runtime behavior after editing app code. Use this skill to confirm a change actually works in a running app — not just that it compiles or type-checks. Combines \u002F_next\u002Fmcp (Next.js's view) with agent-browser (the browser's view). Requires a running `next dev`.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2860,2863,2864,2867,2868,2869],{"name":2861,"slug":2862,"type":13},"Debugging","debugging",{"name":24,"slug":25,"type":13},{"name":2865,"slug":2866,"type":13},"Local Development","local-development",{"name":15,"slug":16,"type":13},{"name":9,"slug":8,"type":13},{"name":2870,"slug":2871,"type":13},"Web Development","web-development","2026-05-22T06:45:28.627735",{"slug":2722,"name":2722,"fn":2874,"description":2875,"org":2876,"tags":2877,"stars":26,"repoUrl":27,"updatedAt":2882},"adopt Partial Prefetching in Next.js apps","Turn on Partial Prefetching in a Next.js app and work through the insights it surfaces. Use when the user wants to enable or adopt Partial Prefetching, flip the `partialPrefetching` flag, opt routes in with `export const prefetch = 'partial'`, audit `\u003CLink prefetch={true}>` calls, or resolve the link-prefetch-partial and instant-shell-url-data insights.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2878,2879,2880,2881],{"name":24,"slug":25,"type":13},{"name":15,"slug":16,"type":13},{"name":2851,"slug":2852,"type":13},{"name":9,"slug":8,"type":13},"2026-07-30T05:31:11.591864",{"slug":2884,"name":2884,"fn":2885,"description":2886,"org":2887,"tags":2888,"stars":2895,"repoUrl":2896,"updatedAt":2897},"turborepo","manage monorepos with Turborepo","Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines,\ndependsOn, caching, remote cache, the \"turbo\" CLI, --filter, --affected, CI optimization, environment\nvariables, internal packages, monorepo structure\u002Fbest practices, and boundaries.\n\nUse when user: configures tasks\u002Fworkflows\u002Fpipelines, creates packages, sets up\nmonorepo, shares code between apps, runs changed\u002Faffected packages, debugs cache,\nor has apps\u002Fpackages directories.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2889,2892,2893],{"name":2890,"slug":2891,"type":13},"CI\u002FCD","ci-cd",{"name":2851,"slug":2852,"type":13},{"name":2894,"slug":2884,"type":13},"Turborepo",30809,"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fturborepo","2026-07-30T05:32:14.920116",{"slug":2899,"name":2899,"fn":2900,"description":2901,"org":2902,"tags":2903,"stars":2911,"repoUrl":2912,"updatedAt":2913},"add-function-examples","add AI function examples for testing","Guide for adding new AI function examples, for testing specific features against the actual provider APIs.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2904,2907,2910],{"name":2905,"slug":2906,"type":13},"AI SDK","ai-sdk",{"name":2908,"slug":2909,"type":13},"Testing","testing",{"name":9,"slug":8,"type":13},25670,"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fai","2026-04-06T18:55:51.318866",{"slug":2915,"name":2915,"fn":2916,"description":2917,"org":2918,"tags":2919,"stars":2911,"repoUrl":2912,"updatedAt":2931},"add-harness-package","add AI SDK harness packages","Guide for adding new AI SDK harness packages. Use when creating a new @ai-sdk\u002Fharness-\u003Cname> package that adapts a coding-agent runtime to HarnessV1.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2920,2923,2924,2927,2930],{"name":2921,"slug":2922,"type":13},"Agents","agents",{"name":2905,"slug":2906,"type":13},{"name":2925,"slug":2926,"type":13},"Harness","harness",{"name":2928,"slug":2929,"type":13},"SDK","sdk",{"name":9,"slug":8,"type":13},"2026-06-18T08:29:19.858737",{"slug":2933,"name":2933,"fn":2934,"description":2935,"org":2936,"tags":2937,"stars":2911,"repoUrl":2912,"updatedAt":2943},"add-provider-package","add new provider packages to AI SDK","Guide for adding new AI provider packages to the AI SDK. Use when creating a new @ai-sdk\u002F\u003Cprovider> package to integrate an AI service into the SDK.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2938,2939,2942],{"name":2905,"slug":2906,"type":13},{"name":2940,"slug":2941,"type":13},"API Development","api-development",{"name":9,"slug":8,"type":13},"2026-04-06T18:55:47.45549",{"slug":2945,"name":2945,"fn":2946,"description":2947,"org":2948,"tags":2949,"stars":2911,"repoUrl":2912,"updatedAt":2962},"adr-skill","create and maintain architecture decision records","Create and maintain Architecture Decision Records (ADRs) optimized for agentic coding workflows. Use when you need to propose, write, update, accept\u002Freject, deprecate, or supersede an ADR; bootstrap an adr folder and index; consult existing ADRs before implementing changes; or enforce ADR conventions. This skill uses Socratic questioning to capture intent before drafting, and validates output against an agent-readiness checklist.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2950,2953,2956,2959],{"name":2951,"slug":2952,"type":13},"ADR","adr",{"name":2954,"slug":2955,"type":13},"Architecture","architecture",{"name":2957,"slug":2958,"type":13},"Documentation","documentation",{"name":2960,"slug":2961,"type":13},"Engineering","engineering","2026-04-06T18:55:50.043694",{"slug":2906,"name":2906,"fn":2964,"description":2965,"org":2966,"tags":2967,"stars":2911,"repoUrl":2912,"updatedAt":2974},"build AI features with Vercel AI SDK","Answer questions about the AI SDK and help build AI-powered features. Use when developers: (1) Ask about AI SDK functions like generateText, streamText, ToolLoopAgent, embed, or tools, (2) Want to build AI agents, chatbots, RAG systems, or text generation features, (3) Have questions about AI providers (OpenAI, Anthropic, Google, etc.), streaming, tool calling, structured output, or embeddings, (4) Use React hooks like useChat or useCompletion. Triggers on: \"AI SDK\", \"Vercel AI SDK\", \"generateText\", \"streamText\", \"add AI to my app\", \"build an agent\", \"tool calling\", \"structured output\", \"useChat\".",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2968,2969,2970,2973],{"name":2921,"slug":2922,"type":13},{"name":2905,"slug":2906,"type":13},{"name":2971,"slug":2972,"type":13},"LLM","llm",{"name":9,"slug":8,"type":13},"2026-04-06T18:55:48.739463",{"slug":2976,"name":2976,"fn":2977,"description":2978,"org":2979,"tags":2980,"stars":2911,"repoUrl":2912,"updatedAt":2984},"capture-api-response-test-fixture","capture API response test fixtures","Capture API response test fixture.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2981,2982,2983],{"name":2940,"slug":2941,"type":13},{"name":2908,"slug":2909,"type":13},{"name":9,"slug":8,"type":13},"2026-04-06T18:55:56.374433",{"slug":2986,"name":2986,"fn":2987,"description":2988,"org":2989,"tags":2990,"stars":2911,"repoUrl":2912,"updatedAt":2994},"develop-ai-functions-example","develop AI SDK function examples","Develop examples for AI SDK functions. Use when creating, running, or modifying examples under examples\u002Fai-functions\u002Fsrc to validate provider support, demonstrate features, or create test fixtures.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2991,2992,2993],{"name":2905,"slug":2906,"type":13},{"name":2908,"slug":2909,"type":13},{"name":9,"slug":8,"type":13},"2026-04-06T18:55:55.088956",68,{"items":2997,"total":3030},[2998,3006,3014,3023],{"slug":4,"name":4,"fn":5,"description":6,"org":2999,"tags":3000,"stars":26,"repoUrl":27,"updatedAt":28},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3001,3002,3003,3004,3005],{"name":21,"slug":22,"type":13},{"name":24,"slug":25,"type":13},{"name":18,"slug":19,"type":13},{"name":15,"slug":16,"type":13},{"name":9,"slug":8,"type":13},{"slug":2823,"name":2823,"fn":2843,"description":2844,"org":3007,"tags":3008,"stars":26,"repoUrl":27,"updatedAt":2854},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3009,3010,3011,3012,3013],{"name":21,"slug":22,"type":13},{"name":24,"slug":25,"type":13},{"name":15,"slug":16,"type":13},{"name":2851,"slug":2852,"type":13},{"name":9,"slug":8,"type":13},{"slug":1093,"name":1093,"fn":2856,"description":2857,"org":3015,"tags":3016,"stars":26,"repoUrl":27,"updatedAt":2872},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3017,3018,3019,3020,3021,3022],{"name":2861,"slug":2862,"type":13},{"name":24,"slug":25,"type":13},{"name":2865,"slug":2866,"type":13},{"name":15,"slug":16,"type":13},{"name":9,"slug":8,"type":13},{"name":2870,"slug":2871,"type":13},{"slug":2722,"name":2722,"fn":2874,"description":2875,"org":3024,"tags":3025,"stars":26,"repoUrl":27,"updatedAt":2882},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[3026,3027,3028,3029],{"name":24,"slug":25,"type":13},{"name":15,"slug":16,"type":13},{"name":2851,"slug":2852,"type":13},{"name":9,"slug":8,"type":13},4]