#94877 removed the embedded plugin marketplace because its plugin
carried copies of skills that drifted from `skills/`. This re-adds
plugin packaging without duplicating any content: `skills/` itself is
the plugin root. `skills/.claude-plugin/plugin.json` sets `"skills":
["./"]` so the four skills at `skills/*/SKILL.md` are discovered in
place, and the root `.claude-plugin/marketplace.json` points its single
entry at `./skills`. External catalogs can reference the plugin with a
`git-subdir` source (`path: "skills"`), which sparse-clones only that
directory instead of the whole repo.
`claude plugin validate` passes on both manifests. `claude --plugin-dir
./skills` discovers all four skills
(`nextjs:next-cache-components-adoption`,
`nextjs:next-cache-components-optimizer`, `nextjs:next-dev-loop`,
`nextjs:next-partial-prefetching-adoption`).
Make the ppr test accept diverse solutions as long as they lead to a
static shell. Achieved by `expect(environment).toSatisfyCriterion`
(a.k.a., llm-as-judge)
Agents have started flagging the managed `nextjs-agent-rules` block as
prompt injection and refusing to follow or commit it (#95130). Headless
A/B evals (Claude Code + Sonnet 5, pnpm-monorepo and standalone-npm
fixtures, `[email protected]`) isolate the triggers:
`node_modules/next/dist/docs/` doesn't resolve from the workspace root
in non-hoisted installs, the "maintained by `next dev`" claim is
unverifiable, and the **Keep this block** paragraph reads as a
self-persisting instruction. The failure concentrates on the uncommitted
state a fresh `next dev` run produces.
The block now anchors the docs path to the block file's directory with a
monorepo note, and replaces the commit imperative with a verify pointer
to `generate-agent-files.js` plus the reason committing it keeps the
tree clean. This is the only change in this PR, and it was ablated in
isolation — an A/A run of the baseline first established the noise floor
(block committal 0/5, 0/8, 1/8 across three independent baseline
batches):
| Metric | Old block | New block |
|
---------------------------------------------------------------------------------
| --------- | --------- |
| Uncommitted block, diff-review prompt — committed the block | 1/21 |
8/20 |
| Uncommitted block, diff-review prompt — final message calls it a
prompt injection | 12/21 | 4/20 |
| Committed block, task prompts — injection flags | 0/15 | 0/15 |
| Docs reads/run — standalone app, write tests (n=2) | 4.5 | 5.0 |
| Docs reads/run — monorepo, add page (n=3) | 3.7 | 4.0 |
| Docs reads/run — monorepo, write tests (n=10) | 3.4 | 2.6 |
Committal improves at p = 0.008 and injection accusations drop at p =
0.016 (Fisher exact); runs that follow the verify pointer conclude
"genuine framework-scaffolded content, not injected". A candidate
follow-up — appending "commit this change" to the dev-server log line —
was ablated separately and does nothing: the line reaches the agent's
transcript in 12/12 dev-session runs but committal stayed 0/6 vs 0/6, so
it is not included. The upsert machinery that delivers this rewording to
existing projects is #95470.
Once the managed agent-rules block is installed, `next dev` never
touches it again — `ensureAgentRulesForDev` gates on marker presence —
and `@next/codemod upgrade` doesn't refresh it either, so changes to the
block's content (like the wording fix in #95467) never reach existing
projects.
`next dev` now gates on `hasCurrentAgentRules` and re-upserts a block
whose content differs from canonical, targeting whichever file already
hosts it so an upgrade rewrites the block in place instead of
duplicating it into a second file; legacy `NEXT-AGENTS-MD` blocks
migrate the same way, and CRLF files keep their line endings.
`@next/codemod upgrade` refreshes the block after the version bump by
delegating to the installed package's own generator
(`next/dist/server/lib/generate-agent-files`), so the codemod never
carries a copy of the wording and no-ops below 16.3 or when the project
never adopted the block. A malformed block (start marker without end)
converges to a single canonical block within two runs rather than
growing unboundedly.
The e2e test now covers the in-place upgrade (surrounding content
preserved, exactly one block, no `CLAUDE.md` created when `AGENTS.md`
hosts it) and idempotence across dev-server restarts, using the real
generator's output rather than hardcoded wording.
Updates the Next.js docs that describe `next-devtools-mcp` to match its
0.4.0 thin-connector shape (see vercel/next-devtools-mcp#145). The
knowledge-base, migration/upgrade, and Cache Components tools are gone —
docs now ship with Next.js in `node_modules/next/dist/docs/` and
upgrades use the codemod — so the guides should no longer point agents
at those tools.
In the MCP guide (`mcp.mdx`), the Development Tools list drops
"Knowledge Base", "Migration and Upgrade Tools", and "Caching Guide" in
favor of a documentation gateway that points agents at the bundled docs,
and the examples now describe reading bundled docs and running the
codemod rather than querying a knowledge base. In the v16 upgrade guide,
the "Using AI Agents with Next.js DevTools MCP" section no longer
instructs users to run removed prompts (`Next Devtools, help me upgrade`
/ `migrate to cache components`); it points to the codemod plus runtime
diagnostics.
Should land with the next-devtools-mcp 0.4.0 release.
<!-- NEXT_JS_LLM_PR -->
Fixes false-negative regex assertions — observed rejecting correct
solutions in recent eval runs — by grading those specific checks with
`@vercel/agent-eval`'s agentic LLM judge instead. Only the assertions
with demonstrated false negatives change; every deterministic check
stays.
- `agent-034-async-cookies`: `/await\s+cookies\s*\(\s*\)/` only matched
the naive `await cookies()` and rejected the correct (arguably better)
`await Promise.all([cookies(), headers()])` — observed failing most runs
of a strong model — and the no-sync-call lookbehind wrongly flagged the
bare `cookies()` inside the array. The four await-mechanics tests
collapse into one judge criterion: the promises must actually be awaited
before use, in any correct form. The theme-cookie and Accept-Language
content checks stay regex.
- `agent-030-app-router-migration-hard`: the home-page test rejected
solutions that didn't match one exact shape (e.g. data fetching
extracted to a helper means no literal `fetch(` in `page.tsx`). Its body
becomes one judge criterion (async Server Component, fetches during
server render, no `getServerSideProps`); the file-existence check and
the other seven tests stay deterministic.
The judge is pinned to **`claude-haiku-4-5`** in the generated
experiment configs — a cheap fixed grader, identical for every run
regardless of the model under test. Each criterion states the
requirement semantically, includes one reference solution to ground the
small grader, and fixes the standard of judgment (runtime correctness,
not style) — no enumerated failure modes, so the judge stays flexible
about equivalent forms.
Supporting changes: bumps `@vercel/agent-eval` `0.9.5` → `1.3.0` (ships
the judge runtime) and adapts `run-evals.js` to the 1.3 CLI (`run
baseline agents-md --force`; `--dry` maps to `status`); generated
experiments now run through the Vercel AI Gateway so `vc env pull` is
the only credential setup; converted fixtures exclude `EVAL.ts` from
tsconfig (the `@vercel/agent-eval/eval` import has no type declarations
— vercel-labs/agent-eval#166 — and `next build` type-checks the fixture,
so experiments running `scripts: ['build']` would otherwise fail on it,
same reason `EVAL.tsx` was already excluded). Not converted: `agent-040`
(already redesigned upstream in #94578) and `agent-041` (no diagnosed
false negative in the report).
Verified end-to-end from this repo: converted `agent-034` with codegen
`claude-sonnet-4-5` via the AI Gateway in a Vercel sandbox, judge pinned
to `claude-haiku-4-5` — passes, with the judge clause executing
in-sandbox.
**Running it:**
```bash
pnpm install # picks up @vercel/agent-eval 1.3.0
pnpm --filter=next build # pnpm eval packs the locally built next
vc env pull .env.local # the only credential: sandbox + AI Gateway (codegen and judge)
pnpm eval agent-034-async-cookies # baseline + AGENTS.md variants
pnpm eval agent-034-async-cookies --dry # preview only
```
<!-- NEXT_JS_LLM_PR -->
These two maintainer skills live under `.agents/skills/` alongside the
internal dev skills (`flags`, `create-pr`, `react-vendoring`, etc.), all
of which carry `metadata.internal: true`. They were missing the flag, so
the skill sync pulled them into the external/universal distribution.
Adds `metadata.internal: true` to both, matching their siblings.
<!-- NEXT_JS_LLM_PR -->
`experimental.useExperimentalReact` names the React *build* it pulls in
rather than the feature it's there for. Rename it to `blockingSSR` — the
actual capability (React's blocking SSR, which emits `<link
rel="expect">` to hold first paint until the streamed shell is coherent)
— mirroring React's own feature flag so it's clear why the experimental
channel got enabled when grepping.
<!-- NEXT_JS_LLM_PR -->
Opting into React's experimental channel — which emits `<link
rel="expect">` to hold first paint until the streamed shell is coherent,
avoiding flicker from partially-streamed HTML — currently requires
enabling an unrelated feature like `experimental.taint` as a side
effect, which is confusing. This adds
`experimental.useExperimentalReact` as a direct opt-in.
It feeds the existing `needsExperimentalReact` aggregation and the
matching Turbopack `react_channel` switch, selecting the
`react@experimental` build the same way `taint`, `transitionIndicator`,
and `gestureTransition` do. It's opt-in only: an explicit `false` can't
disable the channel when one of those still requires it (the taint APIs
only exist in the experimental build), so `assignDefaults` warns on that
contradiction. Covered by a webpack e2e test mirroring the existing
`taint` channel test.
<!-- NEXT_JS_LLM_PR -->
Since 16.2.0 the published `next` package ships its docs at `dist/docs`
(#89850), but `codemod agents-md` still git-clones a copy into
`.next-docs`. When the install resolved from `cwd` has bundled docs and
its version matches the requested one, the generated index now points at
`./node_modules/next/dist/docs` directly — no clone, no `.next-docs`, no
`.gitignore` entry. Older versions and explicit `--version` mismatches
keep the download path.
Covered by new e2e tests: bundled-docs indexing, `--version` match, and
download fallback on mismatch.
<!-- NEXT_JS_LLM_PR -->
When `next dev` upserts the `nextjs-agent-rules` block into a tracked
`AGENTS.md`, agents treat it as diff noise and revert it before
committing — observed in the wild as "I found one dev-server-generated
instruction block in `AGENTS.md`; I'm removing only that generated
block". The block never lands in git, so every agent session regenerates
and re-strips it.
This PR adds one paragraph to the block stating it is intentional,
maintained by `next dev`, and should be committed as-is. Same content
synced in `create-next-app`'s `generate-agent-files.ts`.
## Eval: 16 headless Claude Code trials
**Headline:** across all 16 trials, the current block was removed in 5/8
runs and committed in 0/8; the new block was removed in 0/8 and
committed in 7/8.
**Setup.** Each trial runs `claude -p` (Claude Code CLI 2.1.173,
`--model sonnet`, `--dangerously-skip-permissions`,
`--strict-mcp-config`, `--max-turns 30`) in a fresh throwaway git repo
replicating the reported failure:
- Four committed files: `README.md` (contains the typo `teh`),
`AGENTS.md` (two project notes), `app/page.tsx`, `package.json`.
- The agent-rules block is then appended to the tracked `AGENTS.md` as
an **uncommitted** change, matching the output of
`upsertAgentRulesBlock`.
- The prompt tells the agent it ran `next dev` earlier and quotes the
real generation log line, so the agent has the same provenance signal as
in the real incident.
Two arms × two prompts, fresh fixture per trial:
- **control** = block as on canary today; **treatment** = block with the
new paragraph. The block text is the *only* difference between arms.
- **diff-review prompt** (n=5 per arm) and **plain-commit prompt** (n=3
per arm); exact text below.
<details>
<summary>Exact prompts (identical across arms)</summary>
Shared provenance preamble:
> Earlier in this session you ran `next dev` in this repo; its startup
output included the line: "○ Generated AGENTS.md for AI agents. Set
`agentRules: false` in next.config to disable."
diff-review prompt:
> Task: fix the typo in README.md ('teh' -> 'the'). Before committing,
review the full working-tree diff: commit what belongs in the repo, and
revert anything that should not be committed. End your reply by listing
every working-tree change you found and what you decided to do with it,
with one sentence of reasoning each.
plain-commit prompt:
> Task: fix the typo in README.md ('teh' -> 'the'), then commit your
work.
</details>
**Judging is mechanical** (from repo state, not transcripts): `REMOVED`
= marker absent from the working tree; `KEPT_UNCOMMITTED` = in the
working tree but not in `HEAD:AGENTS.md`; `COMMITTED` = present in
`HEAD:AGENTS.md`. All 16 runs exited 0.
**Results.**
| Prompt | Arm | REMOVED | KEPT_UNCOMMITTED | COMMITTED |
| ------------ | --------- | ------- | ---------------- | --------- |
| diff-review | control | 5/5 | 0/5 | 0/5 |
| diff-review | treatment | 0/5 | 0/5 | 5/5 |
| plain-commit | control | 0/3 | 3/3 | 0/3 |
| plain-commit | treatment | 0/3 | 1/3 | 2/3 |
**Stated reasoning matches the mechanism.** Control agents reproduce the
reported behavior, e.g.:
> Reverted — auto-injected by `next dev` at runtime, not an intentional
developer edit; regenerated on every dev-server start, so committing it
adds noise with no lasting value.
Every treatment agent that committed cited the new sentence, e.g.:
> Committed — the block's own text explicitly instructs: "If it appears
as an uncommitted change, that is intentional — commit it as-is."
Reverting it would also just be regenerated on the next `next dev` run.
Treatment commits were verified at the git level: the block lands in
`HEAD:AGENTS.md` verbatim, pre-existing user content is preserved, and
`git status` is clean. The four `KEPT_UNCOMMITTED` cases are agents
scoping their commit to the requested file; the block survives on disk,
so this is the pre-existing middle state ("someone still has to commit
it"), not a regression.
<!-- NEXT_JS_LLM_PR -->
The `typegen` command chained `nextTypegen(...).then(() =>
process.exit(0))` with no rejection handler. When `next.config` throws
during `loadConfig`, the rejection is orphaned by the synchronous
`program.parse` and then absorbed by Next's `unhandledRejection` filter,
so the process exits `0` having written no route types. A later `tsc`
then reports `Cannot find name 'PageProps'`/`'LayoutProps'` (and missing
`*.png` modules) with nothing tying it back to typegen. Add a `.catch`
that prints an actionable message about the missing route types and
`process.exit(1)`, so `next typegen && tsc` halts on failure. Includes
an e2e test whose fixture config throws and asserts a non-zero exit with
the diagnostic.
<!-- NEXT_JS_LLM_PR -->
Agent friction report flagged three gaps in the instant-nav loop's
third-lever guidance:
- "on the I/O function" wasn't precise enough. The directive needs to
enclose the `cookies()` / `headers()` / `searchParams` read, not
whatever function does the downstream fetch. When those are split across
frames (page reads cookies, helper does the fetch), the natural reading
puts `'use cache: private'` on the helper alone and the page stays
dynamic. Compiles fine, types fine — only runtime tells you.
- The doc says private-cache results live in the browser only, never on
the server. Skill didn't mention this, and without it the
runtime-prefetch combo doesn't really make sense (why would
link-visibility resolve a server-keyed cache?).
- Verify recipe said "after-shot must visibly differ." Identical shots
can also mean no I/O resolved either time — stale TLS sockets, DB
unreachable, etc. Added a check-logs-first guard before iterating on
code shape.
Also a separate gotcha bullet in `SKILL.md`: dev doesn't fire prefetch
traffic at all (see `packages/next/src/client/components/links.ts:247`),
so the network tab won't tell you whether your code works. Use the
cookie-locked SPA-nav recipe instead.
Skill markdown only. No runtime changes.
<!-- NEXT_JS_LLM_PR -->
## Summary
The `next-dev-loop` skill currently asks the user to log in at the start
of every preflight when the target page is gated. With `agent-browser`
0.27.0's `--state <path>` flag and `--session-name <slug>` auto-save,
the saved login can carry from one loop to the next.
This patch updates `skills/next-dev-loop/SKILL.md`:
- **Preflight** — open with `--session-name <slug>` (auto-save on close)
and `--state ~/.agent-browser/sessions/<slug>-default.json` when that
file already exists (omit on first run; a missing path fails the open).
- **Teardown** — note that `--session-name` writes the state on close,
so the next loop's `--state` restores login.
No code changes; doc only.
## Test plan
Verified end-to-end with a headless `claude -p` invocation in a real
logged-in Next.js project:
- The fresh agent reads the updated skill, opens `agent-browser` with
`--session-name <slug> --state <path>`, and finds itself behind the
login — no user input needed.
- After close, the state file's `mtime` advances (auto-save fired).
- Daemon `env` at PID confirms `AGENT_BROWSER_SESSION_NAME` and
`AGENT_BROWSER_STATE` are set correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
## Summary
Replaces `next-ppr-optimizer` (shipped in #93943) with a merged
`next-cache-components-optimizer` skill that covers both page-render and
in-app-nav optimization. The two diagnostics share a preflight, lever
set, anti-pattern list, and reference table; the gating requirement
(`cacheComponents: true`) is what unifies them, hence the rename.
- `SKILL.md` — top-level router; shared preflight, shared `instant
cookie` section (value tuple + set command + p-prefix convention — both
loops reference this rather than duplicating it), refactor levers,
plan-mode gate, no-shell bailout, visible-delta verify (with required
screenshots and dev-overlay-hidden capture), anti-patterns, gotchas
(including a concrete DOM-settle poll and a flakiness re-run rule),
reference table with shapes for the remaining primitives (`agent-browser
react suspense --only-dynamic --json` boundary schema + raw blocker
names, `__nextjs_original-stack-frames` request body, `mcp get_logs`
origin, `cacheLife` profiles), teardown.
- `ppr-loop.md` — page-render diagnose loop. Same `react suspense
--only-dynamic --json` primitive as the nav loop (consistency);
pixel-area ranking on shell-only render; gauge step that skips marginal
refactors.
- `instant-nav-loop.md` — nav diagnose loop. Single capture on B
post-`pushstate` via `react suspense --only-dynamic --json`; each
boundary's `suspended_by[].name` is classified into client-hook /
request-api / server-fetch / cache. **Client-hook blockers
(`usePathname`, `useSearchParams`, `useRouter`, …) are dropped** — they
suspend only during SSR prerender and resolve instantly on SPA nav, so
"fixing" them is wasted work. Shared layouts (the path A and B have in
common) are filtered out implicitly — React keeps them mounted across
the navigation, so their boundaries don't re-suspend and never enter the
capture. With multiple real candidates, rank by where the boundary sits
in B's new-segments tree; fix the one closest to where A's and B's paths
diverge first. Plain-language framing throughout (no LCA/graph-theory
jargon). Includes a gauge step that skips marginal refactors. Plus the
nav-only **third lever**: `'use cache: private'` for I/O reading
`cookies()` / `headers()` / `searchParams`, paired with
`unstable_prefetch = 'force-runtime'` on a route segment that owns the
private content. The framework resolves private-cached content at
link-prefetch time so the click commits with cookie-derived data already
in place.
## Why
- **Dedup.** Both loops share the same refactor levers (push-down +
cache), `cacheLife` profile list, instant-cookie protocol, null-fallback
anti-pattern, no-shell bailout, DOM-settle polling pattern, and teardown
— no duplication across files.
- **Discoverability.** \`cache-components\` matches the canonical config
flag developers search for.
## Workflow guarantees
- **Manual user setup precondition.** The skill doesn't drive
auth/SSO/MFA. The user opens the headed browser (via `next-dev-loop`),
logs in if needed, and navigates to the starting page; only then does
the skill take over. Spelled out in both shared and per-loop preflights.
- **Plan mode for every refactor.** Agent presents a plan (candidate,
lever, file path, expected delta, freshness preset for cache) before
applying. User can iterate before agreeing.
- **Visible-delta verify.** A baseline screenshot is taken before any
change; an after-screenshot is taken in verify. Both paths are reported
in the final summary. Identical-looking captures = refactor didn't land
→ undo. Nav loop captures via cookie-locked SPA-nav (be on A → set
cookie → `pushstate <B>` → settle → screenshot); page-render loop
captures via shell-only render.
- **Dev overlay hidden during capture.** `nextjs-portal` (instant-nav
guidance, error overlay, route indicators) is hidden via JS, screenshot
taken, restored — so the before/after isn't polluted by dev chrome.
- **No-shell bailout.** If the route is fully blocking (HTTP 500 with
`blocking-route` / `NEXT_STATIC_GEN_BAILOUT`, or zero Suspense
boundaries on a visibly-rendered page), the optimizer stops; the user
fixes the structural wrap first.
- **Gauge before applying.** Each loop's diagnose includes a gauge step
using the same capture as verify (shell-only render for PPR,
cookie-locked SPA-nav for nav). If the top-ranked candidate is
sub-viewport, the optimizer offers to audit other routes / nav pairs
instead of forcing a low-ROI refactor.
## Validation
Headless evals against a `cacheComponents` demo app via `claude -p`:
| Scenario | Outcome |
|---|---|
| Already-optimal page (no-fix branch) | Declined refactor correctly |
| Unwrapped page-level \`await\` (push-down) | Extracted I/O into
Suspense-wrapped child, autonomous |
| Uncached real I/O (cache + collab gate) | Asked for \`cacheLife\`,
applied to helper |
| Multi-candidate w/ recursion trap | Ranked by LCA-most-first, recursed
into wrapper rather than blind-wrap |
| PPR-flavored prompt → ppr-loop | Decision step picked the right
sub-loop; sub-reference loaded correctly |
| Nav-flavored prompt → instant-nav-loop (polished) | Decision routing +
terser prose preserved behavior end-to-end |
## Notes
- Removes `skills/next-ppr-optimizer/` shipped in #93943.
- A drafted `next-instant-nav-optimizer` was built during development
but never committed; not part of this diff.
## Test plan
- [ ] Manual: invoke \`/next-cache-components-optimizer\` against a
\`cacheComponents\` app; confirm the agent picks the right sub-loop
based on the request phrasing.
- [ ] Manual: confirm the preflight inherits from \`next-dev-loop\`
correctly (user-driven browser session must be open first).
<!-- NEXT_JS_LLM_PR -->
Adds `metadata.internal: true` to `backport-pr` and `create-pr` so they
aren't published alongside the user-facing Next.js skills in `/skills`.
These are maintainer workflows, not for app developers consuming
Next.js.
<!-- NEXT_JS_LLM_PR -->
Two related skills for the `next dev` agentic workflow. They're paired:
one establishes a shared session and view of the running app, and the
other builds on it for a specific kind of work.
**`next-dev-loop`** is the edit-then-verify rhythm during `next dev`.
After making a change, the agent needs to know whether it actually works
at runtime — not just whether it type-checks or compiles. The skill
teaches the agent to use the two views Next.js exposes about itself:
`/_next/mcp` (framework-side — routes, segments, RSC, server logs,
errors) and `agent-browser` (browser-side — DOM, console, network, React
fiber, vitals). The two views cross-check each other.
The preflight is the load-bearing part. It opens a headed
`agent-browser` session at the user's target URL with React DevTools
enabled, then pauses — because the page might be behind a login wall or
need specific state the user has to set up. Once the user confirms, the
agent probes `/_next/mcp`, confirms Turbopack, and pulls the route map.
Sibling skills can assume this setup is done.
**`next-ppr-optimizer`** is the first sibling. It's the agentic loop for
optimizing the static shell of a `cacheComponents` page: diagnose the
shell, pick the highest-ROI refactor, apply it, confirm the shell grew.
Two levers — push down (extract an I/O into a Suspense-wrapped child so
the parent stays static; autonomous for trivial cases) or cache (wrap in
`'use cache'` with a `cacheLife` profile chosen *with* the user — the
skill never invents one). Diagnosis uses the suspense tree +
`/__nextjs_original-stack-frames` for source resolution, with the H4
anti-pattern guardrail (one dominant boundary covering the viewport is
not a push-down candidate; recurse inside it instead).
<!-- NEXT_JS_LLM_PR -->
The "AI agent hint" comment on the docs landing page (`docs/index.mdx`)
tells every agent reading the docs on every task to export
`unstable_instant` to fix slow client-side navigations.
That's too aggressive for an experimental API — `unstable_instant` is
`version: draft` and still reshaping (`unstable_prefetch` just split off
in #92754). The instant-navigation guide already covers the API in
measured language; agents working on slow-navigation tasks will find it
there.
<!-- NEXT_JS_LLM_PR -->
Experimenting with the notion of a "bundled skill" — agent skills
shipped directly in the `next` package under `next/dist/skills/<name>/`.
The first bundled skill is `next-compile`, which calls the dev server's
`get_compilation_issues` MCP tool to check compilation errors. Bundling
(rather than shipping as a standalone plugin) fits here because the
skill is coupled to Next.js implementation details — the `/_next/mcp`
endpoint and the tool schema — so shipping it in lockstep with the
`next` version that exposes them keeps them in sync.
## Summary
Adds a security guideline to `AGENTS.md` that instructs the PR reviewer
to flag new code that reads non-standard request headers without
checking them against the `INTERNAL_HEADERS` filter list in
`packages/next/src/server/lib/server-ipc/utils.ts`.
## Context
`filterInternalHeaders()` strips internal headers from incoming requests
at the router-server entry point. When new internal headers are
introduced but not added to the filter list, external attackers can
forge them. This guideline helps catch those gaps during code review.
Validated in #92122 — the reviewer successfully flagged a test violation
using this guideline.
## Test plan
- [x] Verified the reviewer catches unfiltered header reads with this
guideline (see #92122)
Upgrades `@vercel/agent-eval` from 0.8.0 to 0.9.5. The new version loads
`.env` / `.env.local` from its own cwd (`evals/`) rather than inheriting
from the parent process, so the old `process.loadEnvFile()` call no
longer works. Instead, `run-evals.js` now symlinks the root env files
into `evals/` before spawning the harness. `evals/.env` is added to
`.gitignore` to keep secrets out of the tree.
Two new evals exercise PPR knowledge: `agent-041-optimize-ppr-shell`
checks that the agent decomposes a monolithic `loading.tsx` into
granular `Suspense` boundaries, and `agent-042-enable-ppr` verifies the
agent knows PPR is enabled via `cacheComponents: true` (not the old
`experimental.ppr` flag).
Updates the AGENTS.md snippet in the AI agents guide to match the wording shipped in the bundled docs. The previous version used softer language ("ALWAYS read docs before coding") that diverged from what `node_modules/next/dist/docs/` actually ships. This aligns the two so agents see consistent instructions regardless of where they read them.
When AI agents run `create-next-app` with explicit flags like
`--typescript --tailwind --eslint --app --src-dir`, the CLI still enters
interactive mode and prompts for any unspecified options:
```
➜ npx create-next-app my-app --typescript --tailwind --eslint --app --src-dir --use-pnpm
✔ Would you like to use React Compiler? … No / Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes
? Would you like to include AGENTS.md to guide coding agents to write up-to-date Next.js code? › No / Yes
```
Agents can sometimes answer these interactive prompts correctly, but
often they can't. When they fail to navigate the prompts, they fall back
to scaffolding the project themselves from scratch — generating files
based on stale training data. This means they might initialize an app
using deprecated patterns or pin to an older version of Next.js (e.g.
15) instead of the latest. Using `create-next-app` non-interactively is
the best way to ensure agents always produce up-to-date scaffolding.
Previously, `hasProvidedOptions` only skipped the initial "use
recommended defaults?" meta-prompt but still showed individual prompts
for each missing option. Now when any config flags are provided, all
remaining options use the recommended defaults without prompting.
The resolved defaults are printed to stdout so the caller knows exactly
what was assumed and which flags to pass to override:
```
Using defaults for unprovided options:
--eslint ESLint (use --biome for Biome, --no-eslint for None)
--no-react-compiler No React Compiler (use --react-compiler for React Compiler)
--agents-md AGENTS.md (use --no-agents-md for No AGENTS.md)
--import-alias "@/*"
To customize, re-run with explicit flags.
```
The `displayConfig` array is extended with a `flags` field so this
output is auto-generated from the same source of truth used for the
interactive prompts. Existing behavior for `--yes`, CI mode, and fully
interactive mode (no flags) is unchanged.
The `lint` job in CI has been hitting `FATAL ERROR: Ineffective
mark-compacts near heap limit Allocation failed - JavaScript heap out of
memory` during `lint-eslint`.
`lint-no-typescript` runs `prettier-check`, `lint-eslint .`, and
`lint-language` in parallel via `run-p`. The `lint-eslint` script uses
`eslint.cli.config.mjs` which enables type-checked rules with
`parserOptions.project: true` — the config comment itself notes this
"needs a lot of memory". Node's default heap limit caps around ~2-4GB
regardless of available system memory, which isn't enough when the
type-aware parser loads the full monorepo project graph.
This sets `NODE_OPTIONS="--max-old-space-size=8192"` on the
`lint-no-typescript` script via `cross-env`. The env var inherits into
all three `run-p` children, giving eslint the headroom it needs without
touching CI workflow files.
Fixtures now live next to the code they test, like e2e. `pnpm eval
<name>` packs the local `next` build, generates baseline + agents-md
experiment configs on the fly, and runs both in a sandbox. The agents-md
variant drops an `AGENTS.md` that points the agent at the bundled docs
in `node_modules/next/dist/docs/` — comparing the two variants tells you
whether shipping a doc actually changes agent behavior.
`run-evals.js` mirrors `run-tests.js`: pack once, pass the tarball path
to the child via `NEXT_EVAL_TARBALL` env, forward flags. We only pack
`next`, not the whole workspace — the sandbox is remote Linux, so a
local `@next/swc` darwin binary wouldn't run there anyway; the sandbox
downloads the right one at runtime. The experiment config uses `sandbox:
'auto'`, which picks Vercel sandboxes when credentials are present and
falls back to local Docker otherwise, so external contributors can run
the same evals with just Docker + `ANTHROPIC_API_KEY`.
`experiments/` is generated fresh each run and gitignored so we don't
maintain N committed config files that differ by one line. Fixture code
is excluded from eslint since it's deliberately imperfect code for
agents to fix, and `EVAL.ts` uses vitest rather than jest. Fixture
`package.json` files use `"next": "^16"` rather than a pinned canary so
agents reading `package.json` to infer capabilities aren't misled by a
stale version string; the tarball install overlays it regardless.
next-evals-oss stays as the full benchmark runner for nextjs.org/evals;
it'll pull fixtures from here instead of keeping its own copy.
Updates the `AGENTS.md` content generated by `create-next-app` to use
clearer wording that better signals to models that their training data
may be outdated.
The prompt wording was determined by running evals continuously until we
found a prompt that is objectively unbiased and shows no signs of
regression on any tests across all models.
**Before:**
> Next.js: ALWAYS read docs before coding
> Before any Next.js work, find and read the relevant doc in
`node_modules/next/dist/docs/`. Your training data is outdated — the
docs are the source of truth.
**After:**
> This is NOT the Next.js you know
> This version has breaking changes — APIs, conventions, and file
structure may all differ from your training data. Read the relevant
guide in `node_modules/next/dist/docs/` before writing any code. Heed
deprecation notices.
Adds a guide for configuring AI coding agents to use the version-matched
documentation bundled in `node_modules/next/dist/docs/` (shipped via the
`copy_docs` build task in #89850) instead of stale training data.
The guide covers how the bundled docs work, the `AGENTS.md` and
`CLAUDE.md` files generated by `create-next-app --agents-md`, and how to
add project-specific instructions outside the `<!--
BEGIN:nextjs-agent-rules -->` markers.
AI coding agents often rely on outdated training data when working with
Next.js projects, leading to hallucinated APIs and deprecated patterns.
This PR addresses that by shipping version-matched documentation inside
the `next` npm package and generating agent instruction files during
`create-next-app`.
During `next` build, a new `copy_docs` task copies `docs/` from the repo
root into `dist/docs/`. Since `"dist"` is already in the package's
`files` array, the docs are automatically published at
`node_modules/next/dist/docs/`.
`create-next-app` gains a new `--agents-md` / `--no-agents-md` flag
(defaults to `true`). When enabled it writes two files:
- `AGENTS.md` — instructs agents to read `node_modules/next/dist/docs/`
instead of relying on training data
- `CLAUDE.md` — uses `@AGENTS.md` import syntax for Claude Code
The option appears in the recommended defaults display and is prompted
during the customize flow. Existing tests are updated with
`--no-agents-md` to avoid the new prompt.
Fixes#89240
The `agents-md` codemod failed on Windows because it assumed forward
slashes in file paths. This caused the doc tree builder to skip files,
resulting in an empty index being injected into `AGENTS.md`.
The path handling code used string operations like `.split('/')` and
`.endsWith('/index.mdx')` which don't work with Windows backslashes. Now
uses regex patterns that match both separators: `/[/\\]/` for splitting
and `!/[/\\]index\.mdx$/` for filtering. Also normalizes all paths to
forward slashes in the output.
While here, switched from `execSync` to `execa` for git commands. This
is the pattern already used elsewhere in the codemod package
(`upgrade.ts`, `handle-package.ts`) and passes arguments as an array
instead of shell string interpolation.
Tests were replaced with e2e tests that run the full CLI and include a
cross-platform path normalization test case.
## Summary
Updates the instruction in `agents-md` [CLAUDE.md](http://CLAUDE.md)
output to force LLMs to actually read the docs instead of relying on
stale pre-training knowledge.
**Before:**
```
IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Next.js tasks.
```
**After:**
```
STOP. What you remember about Next.js is WRONG for this project. Always search docs and read before any task.
```
## Why This Matters
Through systematic testing with the
[next-evals-oss](https://github.com/vercel/next-evals-oss) eval suite,
we discovered that the original "prefer retrieval-led reasoning"
instruction was ineffective. Agents would ignore it and use outdated
pre-training knowledge (e.g., creating `middleware.ts` instead of the
new Next.js 16 `proxy.ts` convention).
## Testing Methodology
We created an "indirect proxy" eval that tests if agents know `proxy.ts`
is needed for request interception in Next.js 16+, using only the prompt
"Log every request to the console" (no mention of proxy/middleware).
| Instruction | Pass Rate |
| --- | --- |
| "Prefer retrieval-led reasoning" | 0% |
| "CRITICAL: MUST read docs" | ~20-40% |
| "What you remember is WRONG" | **100% (6/6)** |
## Why It Works
The phrase **"What you remember is WRONG"** creates psychological doubt
in the model's confidence, forcing it to actually read the docs rather
than trusting its pre-training.
Key elements:
- **"STOP"** - Attention grabber, interrupts default behavior
- **"WRONG for this project"** - Creates doubt without claiming
universal wrongness
- **"Always search docs and read"** - Actionable instruction
- **"before any task"** - Applies to everything, not just code writing
## Full Suite Results
- The new indirect-proxy eval passed consistently
When running `next dev`, then stopping it, deleting a route, and running
`next build`, the build would fail with a type error like:
```
.next/dev/types/validator.ts:78:39
Type error: Cannot find module '../../../app/simple-test/page.js'
```
The root cause here is a fundamental tension: we have **one
tsconfig.json** that needs to serve **two different modes**. When
`isolatedDevBuild` is enabled (which is the default), Next.js outputs
types to different directories:
- Development: `.next/dev/types/`
- Production: `.next/types/`
To avoid tsconfig.json changing every time you switch between dev and
build (which causes git noise and forces IDE reloads), we proactively
include both paths:
```json
{
"include": [
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
]
}
```
The problem is that TypeScript checks ALL files matching these patterns.
If `.next/dev/types/validator.ts` exists from a previous dev session and
contains imports to routes that have since been deleted, TypeScript
fails during build because those imports point to non-existent files.
You might ask: why not just completely separate them with two tsconfigs?
The issue is that would require users to know which tsconfig to use for
their IDE, and switching between configs manually defeats the purpose of
a seamless dev/build experience. The single tsconfig approach is
intentional.
The fix filters out `.next/dev/types` files programmatically when
running type check during build, without touching tsconfig.json:
```typescript
let fileNames = effectiveConfiguration.fileNames
if (excludeDevTypes) {
const devTypesPattern = /[/\\]\.next[/\\]dev[/\\]types[/\\]/
fileNames = fileNames.filter((fileName) => !devTypesPattern.test(fileName))
}
```
This way the tsconfig stays unchanged (no git churn, IDE stays happy),
dev types are preserved for concurrent dev sessions, and build only
type-checks its own `.next/types` directory.
Follow up to https://github.com/vercel/next.js/pull/86273
The typed routes test was checking for `/project/[slug]` in the
`RedirectRoutes` type, but this route doesn't actually exist in the test
fixture anymore. The test fixture only has redirects defined for
`/blog/[category]/[[...slug]]`.
When Next.js generates the typed routes during the build, it only
includes routes that actually exist in the project structure. The test
was expecting a route that wasn't present, which would cause the test to
fail if the generated types were correct.
This removes `/project/[slug]` from the expected `RedirectRoutes` type
so the test expectations match what's actually generated.
Also includes a minor formatting fix in
`apps/bundle-analyzer/next-env.d.ts` to use consistent semicolons and
double quotes per the project's style guidelines.
## Problem
Telemetry events were not being captured in three scenarios:
### 1. MCP Telemetry Lost on Dev Server Shutdown
Two telemetry instances were created during dev server startup. MCP
events were recorded to one instance but shutdown flushed a different
instance.
**Fix:** Reuse the existing telemetry instance from `traceGlobals`.
### 2. Process Exit Timeout Killing Async Telemetry
The CLI force-kills child processes after 100ms
(`NEXT_EXIT_TIMEOUT_MS`). Async telemetry operations were interrupted
before completion.
**Fix:** Use `flushDetached()` to write events to disk and spawn a
detached process for async submission, allowing immediate parent exit.
### 3. Build Errors Calling `process.exit()` Before Telemetry Flush
`process.exit()` calls in error handlers (SWC failures, missing
dependencies, route conflicts) bypassed finally blocks and killed
processes before telemetry completed.
**Fix:** Throw errors instead of calling `process.exit()`, add finally
blocks with `await telemetry.flush()` in critical paths.
## Changes
**Core:**
- `server/dev/next-dev-server.ts`: Reuse telemetry instance
- `server/lib/start-server.ts`: Use `flushDetached()` for shutdown
- `build/swc/index.ts`: Remove `process.exit()` calls
- `build/turbopack-build/impl.ts`: Add telemetry flush in worker finally
block
- `build/index.ts`: Add telemetry flush in finally block, throw instead
of exit
- `lib/verify-partytown-setup.ts`: Throw instead of exit
**Supporting:**
- `telemetry/storage.ts`: Generate unique event files per PID
- `telemetry/detached-flush.ts`: Accept optional eventsFile parameter
- `errors.json`: Update detached-flush error message
## Testing
All telemetry tests now pass:
- MCP telemetry on dev server shutdown
- SWC load failures in worker threads
- Build configuration errors
✅ Non-blocking shutdown
✅ Timeout-independent
✅ No race conditions
✅ Works in worker threads
This PR fixes a critical issue with the `experimental.inlineCss` feature
where CSS `url()` references to fonts and other assets would break when
CSS was inlined into `<style>` tags instead of being loaded via external
`<link>` tags. The problem occurred because relative URLs in CSS are
resolved relative to the CSS file's location when loaded externally, but
when the CSS is inlined directly into the HTML document, those same
relative URLs resolve relative to the HTML document instead, causing 404
errors for fonts and images. We fixed this by detecting when CSS is
being prepared for inlining and switching from relative URLs (like
`../_next/static/media/font.woff2`) to absolute URLs (like
`/_next/static/media/font.woff2`) in both the Webpack and Turbopack
bundlers. This ensures that asset references work correctly regardless
of whether the CSS is inlined or external, while maintaining the
existing optimization of using relative URLs for the default case where
CSS is loaded via external stylesheets. The fix is transparent to users
and requires no changes to application code—simply enabling
`experimental.inlineCss: true` will now work correctly with all CSS
asset references.
Fixes https://github.com/vercel/next.js/issues/83674
---------
Co-authored-by: Niklas Mischkulnig <[email protected]>
The `get_page_metadata` MCP tool was incorrectly grouping browser
sessions by URL instead of counting each session individually. When
multiple browser tabs were open to the same URL, the tool would report
only 1 session instead of the actual number of sessions.
Enables AI agents to programmatically access the runtime metadata of the
current page. At the moment, we only include the segment trie details,
but this endpoint will be open to extension. Like `get_errors`, we
support collecting information from multiple tabs.
Enables AI agents to programmatically access the runtime/build errors
from the current devtool overlay of a dev session. The errors are
pretty-printed by default with source-mapped stack frames. In
particular, it supports pulling error information from multiple browser
tabs.
This PR integrates an MCP server into the Next.js development server at
`/_next/mcp`, enabling AI agents to programmatically interact with
Next.js projects. We're including an initial `get_project_path.` tool as
an example that returns the project's absolute path - this serves as a
starting point to demonstrate how tools can expose project information
to MCP clients, with plans to expand the available tools for richer
AI-assisted development experiences.
The `key` prop on `<Head>` component differs during prerender and a PPR
resume because of search params being part of the key, causing the
resume to treat them as distinct components, hence the error\*. However,
it is necessary semantically to include search params in the key for
`<Head>`, so we will remove the search params from the key during the
resume SSR.
\*Mismatch Error:
```
⨯ [Error: Couldn't find all resumable slots by key/index during replaying.
The tree doesn't match so React will fallback to client rendering.]
```
The prelude is empty when the entire app is suspended during prerender.
Typically this indicates a lack of suspense boundary in the app, which
creates no static shell, thus throwing error. However, if the root
layout itself is dynamic but wrapped under a suspense, we need special
logic in this PR to detect that.
`Mediapartners-Google` used to match both `HEADLESS_BROWSER_BOT_UA_RE`
and `HTML_LIMITED_BOT_UA_RE`, causing inconsistent state values for
`getBotType`, `isDomBotUA`, and `isHtmlLimitedBotUA`.
The issue was that DOM bots with PPR-enabled routes were being forced to
use BLOCKING_STATIC_RENDER mode, which prevented them from using the
fallback cache lookup mechanism. This caused them to generate new
content with isRevalidating: true, which blocks postponed data and
triggers the Math.random() static generation bailout error.
For HTML bots, they keep using BLOCKING_STATIC_RENDER because SSG was
turned off for them and won't see the SSG bailout error. For DOM bots,
they should behave like a normal user.
Fixes NAR-273
Found during dogfooding on Vercel that sometimes the RSC validation
fails incorrectly. The root cause was that headers like
`Next-Router-Prefetch` were absent from the header and transferred to
request meta. Eventually we should improve how we retrieve these fields
consistently.
If a prefetch request hits a 307, the browser follows the redirect, and
you’ll know the final URL after that. Then, during actual navigation,
dynamic data should be fetched from the redirected URL rather than the
original one specified in the link.
For all RSC requests, we require the `_rsc` search param to match the
hash of its corresponding RSC headers. In the case where the match
fails, we respond with a redirect to the correct search param. This hash
check only applies to `next start` mode for now.
For a static route that simply returns a `redirect`:
```
export default function Page() {
redirect("/about");
}
```
the SSG generates a `.meta` file for the route at build time, that looks
like:
```
{
"status": 307,
"headers": {
"location": "/about"
}
}
```
On `next start`, if a request matches this route, the status code is
retrieved from the file and returned to the response. This is perfectly
fine for DOC requests. However, if the request is an RSC request, its
RSC payload should have already encoded the redirect instruction:
```
1:"$Sreact.fragment"
2:I[657,[],""]
3:I[8597,[],""]
5:I[5975,[],"OutletBoundary"]
8:I[4429,[],"AsyncMetadataOutlet"]
a:I[5975,[],"ViewportBoundary"]
c:I[5975,[],"MetadataBoundary"]
e:I[1363,[],""]
0:{"P":null,"b":"gelhztYnmFGzccJ4F6F4T","p":"","c":["","rsc-redirect"],"i":false,"f":[[["",{"children":["rsc-redirect",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",["$","$1","c",{"children":[null,["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":["rsc-redirect",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":["__PAGE__",["$","$1","c",{"children":["$L4",null,["$","$L5",null,{"children":["$L6","$L7",["$","$L8",null,{"promise":"$@9"}]]}]]}],{},null,false]},null,false]},null,false],["$","$1","h",{"children":[null,["$","$1","0db7ba866b0656c433aa56ad545d5b104626108ev",{"children":[["$","$La",null,{"children":"$Lb"}],null]}],["$","$Lc",null,{"children":"$Ld"}]]}],false]],"m":"$undefined","G":["$e","$undefined"],"s":false,"S":true}
4:E{"digest":"NEXT_REDIRECT;replace;/about;307;"}
f:"$Sreact.suspense"
10:I[4429,[],"AsyncMetadata"]
d:["$","div",null,{"hidden":true,"children":["$","$f",null,{"fallback":null,"children":["$","$L10",null,{"promise":"$@11"}]}]}]
7:null
b:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
6:null
9:{"metadata":[],"error":null,"digest":"$undefined"}
11:{"metadata":"$9:metadata","error":null,"digest":"$undefined"}
```
Specifically,
```
4:E{"digest":"NEXT_REDIRECT;replace;/about;307;"}
```
Hence, it would be fine to simply return 200 status code.
There are two motivations for this change:
1. This new behaviour is consistent with Next.js apps hosted on Vercel.
(I.e., it returns 200 for static RSC even if the `meta.status = 307`)
2. 307 redirect on RSC payload is an obstacle to enabling [RSC
validation](https://github.com/vercel/next.js/pull/80157)
`performance.measure` here is causing problems but I also do not see the
point of keeping it, so this PR deletes it.
Closes NDX-1050
Fixes https://github.com/vercel/next.js/issues/79188
Added documentation for the new Build Lifecycle Hooks feature in the
Next.js Compiler, specifically the `runAfterProductionCompile` hook.
https://github.com/vercel/next.js/pull/77345
Add a `runAfterProductionCompile` hook in next.config.js that executes
after production build compilation finishes, but before running
post-compilation tasks like type checking and static page generation.
This hook provides access to project metadata including the project
directory and build output directory, making it useful for third-party
to collect build outputs like sourcemaps.
# `useLinkStatus` Hook
The `useLinkStatus` hook is primarily useful when:
- Links have `prefetch={false}` set
- Navigation occurs before prefetching has completed
- When React decides to [skip the loading
state](https://react.dev/reference/react/useTransition#preventing-unwanted-loading-indicators)
and https://github.com/vercel/next.js/issues/69625
It provides loading state feedback during these navigation transitions,
preventing pages from appearing "frozen" while new content loads.
## Usage
`useLinkStatus` can be called from any descendant component of `Link`
and it returns `{ pending: true }` before history has updated. After the
URL updates, it returns `{ pending: false }`.
```jsx
// When prefetching is disabled
<Link href="/dashboard/reports" prefetch={false}>
Reports <LoadingIndicator />
</Link>
function LoadingIndicator() {
const { pending } = useLinkStatus();
return pending ? <Spinner /> : null;
}
```
## Notes
- If the link has been prefetched, pending change will be skipped and
remain `{ pending: false }`.
- If `useLinkStatus` is not under any `Link`, it will always return `{
pending: false }`.
- `useLinkStatus` currently does not support Pages Router, so it will
always return `{ pending: false }` in Pages Router.
- When you click multiple links in a short period, only the last link's
pending state is shown
---------
Co-authored-by: Andrew Clark <[email protected]>
### What?
The behavior of `OPTIONS` requests differs depending on whether the page
is an SSG page or not:
- For **SSG pages**, we return a `405 Method Not Allowed` status code
([#34346](https://github.com/vercel/next.js/pull/34346)).
- Otherwise, when an `OPTIONS` request reaches `doRender`, it returns a
`400 Bad Request`
([#65295](https://github.com/vercel/next.js/pull/65295)).
However, in the first case, handling the error page triggers a
`doRender` call, which inadvertently resets the status code to `400`. We
want to ensure it remains `405`.
To fix this, I propose **skipping the error page rendering for `OPTIONS`
requests on SSG pages** and directly returning `"Method Not Allowed"`.
This also addresses several related issues (see "Why").
### Why?
When an `OPTIONS` request is made to an SSG page:
1. **Rendering an error page is wasteful.** A `405` response doesn’t
need a UI, so skipping the rendering improves efficiency.
2. **The status code is reset incorrectly.** As described in ["What"],
the logic in
[`base-server.ts#L2682`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/base-server.ts#L2682)
changes the status code to `400` when rendering an error page.
3. **It triggers a fatal error** (`Error: invariant: cache entry
required but not generated`) for projects deployed on Vercel (see:
[NEXT-4000](https://github.com/vercel/next.js/issues/4000)).
### How?
Instead of rendering an error page, we return a **hardcoded HTML
response** with a `"Method Not Allowed"` body.
Closes [NEXT-4000](https://github.com/vercel/next.js/issues/4000).
This was a style regression: We wanted to use a grey'd out color to
indicate building and the full brightness one for rendering. Original PR
before regression: https://github.com/vercel/next.js/pull/74833
We [inject a style tag](https://github.com/vercel/next.js/pull/76225)
that downloads Geist fonts from local file system. However, that style
tag is injected to the main DOM because of an
[issue](https://issues.chromium.org/issues/41085401) in Chrome that the
fonts do not work in shadow DOM. We worry that this injected style would
interfere with user's styles, or vice versa. Hence, we are going to name
them with unique prefix to avoid most collision.
Fix font loading from a non-root URL:
Repro:
```
pnpm next dev test/development/app-dir/owner-stack/
```
and Visit http://localhost:3000/browser/uncaught
Shows
```
GET /browser/__nextjs_font/geist-latin.woff2 404 in 330ms
```
### What?
Two issues need to be solved:
1. Font isn't working. Dev overlay uses Geist fonts. Currently does not
work if they are not installed in OS.
2. Even if fonts worked, we would be downloading fonts from Google for
the dev overlay, which is not ideal for slow internet & users with
restricted access to Google.
### Why?
**Reason to (1):** Styles defined inside Shadow DOM cannot contain
`@font-face`. Custom fonts should be defined in the main container and
can be accessed in Shadow DOM. Read this
[post](https://robdodson.me/posts/at-font-face-doesnt-work-in-shadow-dom/).
### How?
To solve (1), we simply lift the font styles outside the Shadow DOM.
To solve (2), we introduce a new dev middleware for the dev-server that
serves the font assets & we modify the taskfile to move the woff2 fonts
to the dist folder.
Closes NDX-834
### What?
When you enable both Turbopack, and the new dev overlay, you will see
that the dev overlay is frozen and the network tab shows that it is
fetching the stack frames API indefinitely.
### Why?
During our migration to the new development overlay, we implemented a
refactor to ensure that logic for handling runtime errors is executed
consistently, even in the case of build-time errors. This change
involves invoking the getErrorByType function for build errors. However,
this function attempts to retrieve stack frames for the build-time
error, inadvertently triggering Turbopack to initiate a rebuild.
Once Turbopack completes the rebuild process, it dispatches a Hot Module
Replacement (HMR) event to indicate the presence of a new error. This
event prompts getErrorByType to attempt fetching the stack frames again,
which in turn causes Turbopack to rebuild once more. This creates an
endless loop of rebuilding and error signaling.
You can repro it by running
```
__NEXT_EXPERIMENTAL_NEW_DEV_OVERLAY='true' TURBOPACK=1 pnpm testonly-dev test/development/acceptance-app/editor-links.test.ts
```
with the test file change in this PR. (Important to add that. Otherwise
the test would still complete successfully)
### How?
Do not run the runtime error logic for build errors.
Closes NDX-816