mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
skills(cc-adoption): tighten requires, sequencing, terminology (#95225)
### What? Tightens `skills/next-cache-components-adoption/SKILL.md` based on four agent friction logs. ### Why? - Non-interactive strategy default was ambiguous on small apps. - `experimental.dynamicIO` is fatal at config-schema time, not segment-config time — needs its own bullet. - The `cache-components-instant-false` codemod doesn't translate `revalidate`/`dynamic`/`fetchCache`; agents expected an immediate passing build and got a failed one. - "Build passes immediately" overpromised — only true once sync-IO + segment configs are translated. - "Green" → "passing" for clarity. ### How? 13 insertions / 7 deletions, single file. New `dynamicIO` bullet, restructured pre-codemod work into a numbered list (sync-IO + segment configs), softened the Incremental headline, simplified the non-interactive default. No docs material duplicated — recipes stay in the [migration guide](https://nextjs.org/docs/app/guides/migrating-to-cache-components). <!-- NEXT_JS_LLM_PR -->
This commit is contained in:
@@ -21,11 +21,13 @@ Enable Cache Components on an app and walk it to a passing build. This skill seq
|
||||
- `npx @next/codemod@latest upgrade latest` to apply the version-to-version codemods.
|
||||
- Read the relevant [version upgrade guide](https://nextjs.org/docs/app/guides/upgrading) (e.g. [Version 16](https://nextjs.org/docs/app/guides/upgrading/version-16)) for what the codemod doesn't cover.
|
||||
|
||||
- **No incompatible config keys.** `cacheComponents: true` errors on any file that still exports `dynamic`, `revalidate`, or `fetchCache`, and on `experimental.dynamicIO` (renamed). `experimental.useCache` still works as a deprecated alias but should be migrated for clarity. **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://nextjs.org/docs/app/guides/migrating-to-cache-components#enable-cache-components). If a value can't be cleanly translated yet, leave a `// TODO: Cache Components adoption — restore revalidate = 3600` comment so the loop picks it up.
|
||||
- **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://nextjs.org/docs/app/guides/migrating-to-cache-components#enable-cache-components). If a value can't be cleanly translated yet, leave a `// TODO: Cache Components adoption — restore revalidate = 3600` comment so the loop picks it up. The `cache-components-instant-false` codemod does not touch these.
|
||||
|
||||
- **`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.
|
||||
|
||||
### notes
|
||||
|
||||
- **No green 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 green. Note this in your starting summary so it doesn't read as a regression.
|
||||
- **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.
|
||||
|
||||
- **Offline docs.** Offline copies of guide links live under `node_modules/next/dist/docs/`, with the directory layout numbered for ordering (e.g. `node_modules/next/dist/docs/01-app/02-guides/migrating-to-cache-components.md`). The trailing filename matches the slug. If you can't predict the numbered prefix, `find node_modules/next/dist/docs -name '<slug>.md'` resolves it. The `/docs/messages/*` error pages are not bundled. If offline docs are missing entirely, run `npx @next/codemod@latest agents-md` to write a version-matched index into `AGENTS.md` / `CLAUDE.md`.
|
||||
|
||||
@@ -35,7 +37,7 @@ There's one loop: walk the route tree top-down, one feature at a time, adopting
|
||||
|
||||
The choice in step 1 is whether to silence the validation errors first or fix them as you go. Either way the loop is the same:
|
||||
|
||||
- **With a quiet pre-step (Incremental).** Run the codemod to opt every page and layout out of validation. The build passes immediately, you ship that as its own PR, and then start the loop — removing one opt-out at a time and adopting that route. Picks the work apart into small reviewable PRs.
|
||||
- **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`/`dynamic`/`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. Picks the work apart into small reviewable PRs.
|
||||
- **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.
|
||||
|
||||
In 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. Expect to spend most of the time in the loop, not in the pre-step.
|
||||
@@ -57,7 +59,7 @@ Three classes of blocker bite agents in this order:
|
||||
Prefer `next dev` over `next build` while you work.
|
||||
|
||||
- **`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 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.
|
||||
- **`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 — even when the build surfaces a clean compile error, you still don't know what ended up in the static shell vs streamed; the build's clean error messages are the seductive part. 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/admin/**/page.tsx"` — not URL paths; `--debug-build-paths="app/(marketing)/about/page.tsx"` — not `/about`; `--debug-build-paths="app/admin"` 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.
|
||||
- **`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 clean compile error doesn't tell you what ended up in the static shell vs 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/admin/**/page.tsx"` — not URL paths; `--debug-build-paths="app/(marketing)/about/page.tsx"` — not `/about`; `--debug-build-paths="app/admin"` 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.
|
||||
|
||||
Every blocking error has a docs page — open it. Both the dev overlay and the build terminal print a `https://nextjs.org/docs/messages/<slug>` 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. (`/docs/messages/*` pages aren't bundled offline; if you have no network, fall back to the per-API guides under `node_modules/next/dist/docs/` and note the limitation when you report back.)
|
||||
|
||||
@@ -67,7 +69,7 @@ A passing build or a cleared overlay isn't proof the route actually behaves —
|
||||
|
||||
In preference order:
|
||||
|
||||
1. **[`next-dev-loop`](https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop) — strongly preferred.** Cross-checks `/_next/mcp` 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 orders of magnitude richer than poking at `next dev` by hand.
|
||||
1. **[`next-dev-loop`](https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop) — strongly preferred.** Cross-checks `/_next/mcp` 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.
|
||||
|
||||
Install it before starting the loop. Don't wait until you hit something `next dev` alone can't explain. Run:
|
||||
|
||||
@@ -91,14 +93,18 @@ In preference order:
|
||||
|
||||
Ask the user. Phrase it as a PR-shape question, not a sizing call. Never use the internal labels (Incremental, Direct, milestone A) 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 `// TODO: Cache Components adoption` markers double as your work queue for next session). Don't pick on their behalf.
|
||||
|
||||
In a non-interactive run, default to **Incremental** for a multi-route app and **Direct** for a handful-of-routes app, and say so when you start.
|
||||
If there's no user to ask, default to **Incremental** and document the choice.
|
||||
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
### incremental
|
||||
|
||||
Before invoking the codemod, fix the sync-IO blockers it can't. Grep the whole repo for `new Date()`, `Date.now()`, `Math.random()`, and `crypto.randomUUID()` (not only `app/**/layout.{js,jsx,ts,tsx}` — the read might live in any component imported by a layout). Translate each match using the recipe from its `blocking-prerender-*` error card: cache stable values with `"use cache"`; wrap per-request values in `await connection()` + `<Suspense>`. A layout with two distinct reads (e.g. a copyright year and a "last updated" stamp) usually needs two distinct fixes.
|
||||
Before invoking the codemod, fix the two classes of blocker it can't.
|
||||
|
||||
1. **Sync-IO at module/render time.** Grep the whole repo for `new Date()`, `Date.now()`, `Math.random()`, and `crypto.randomUUID()` (not only `app/**/layout.{js,jsx,ts,tsx}` — the read might live in any component imported by a layout). Translate each match using the recipe from its `blocking-prerender-*` error card.
|
||||
|
||||
2. **Incompatible segment configs.** Grep for `^export const (revalidate|dynamic|fetchCache)` across `app/` and translate per the `requires` note above. The codemod does not touch them; leaving them in place fails the build after the codemod.
|
||||
|
||||
The 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.
|
||||
|
||||
@@ -118,7 +124,7 @@ If the codemod isn't available (older `@next/codemod`, sandboxed environment, of
|
||||
export const instant = false
|
||||
```
|
||||
|
||||
The 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 green. If only the root were opted out, removing it would re-arm validation for the entire app at once.
|
||||
The 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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -170,7 +176,7 @@ Used when there's no way to drive a browser — CI, sandbox, the user has no `ne
|
||||
Per route:
|
||||
|
||||
- Remove the opt-out (Incremental) or target the failing route (Direct).
|
||||
- Rebuild with `--debug-build-paths app/<route>/**` (only that route) or `--debug-prerender` (full build, but past the first failure). Route green? Move on. Still blocking? Fix.
|
||||
- Rebuild with `--debug-build-paths app/<route>/**` (only that route) or `--debug-prerender` (full build, but past the first failure). Route passing? Move on. Still blocking? Fix.
|
||||
- Fix — fetch the docs page linked from the error (`https://nextjs.org/docs/messages/<slug>`), apply the recipe from there.
|
||||
- Re-check siblings if the fix touched shared code.
|
||||
- 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.
|
||||
|
||||
Reference in New Issue
Block a user