Sync the React Ledger stack at c126173606 into the stable and
experimental vendored packages using local builds. Includes Ledger
forwarding and work-batch completion.
> [!TIP]
> Recommended to review commit by commit.
This PR adds `next upgrade --experimental-ai="security"` flag (alias
`--ai`), which is targeted to help users leverage agents to upgrade
their app to the safe major version when their app's Next.js version has
any security advisories.
Once the command is ran from the user, Next.js will detect the installed
agent harness in user's device, currently limited to Codex and Claude,
and will proceed with starting an agent session once approved. If it is
called within an agent session, the work will continue off within that
agent.
`next upgrade --ai` simply does two things:
- prepare the relevant context to temporary dir
- print hand off prompt, guiding to read those context
The context will guide the agent to run relevant codemods and migration
checklist to proceed. This PR is a base core of the workflow, and will
have wrappers of entry point around this. Also, will add "latest" and
"future" as follow up, which will cover the app to be always latest, and
adopt the future defaults like Cache Components.
This PR also sets up the evals infra and adds evals.
Became orphaned in https://github.com/vercel/next.js/pull/97792. Another
script and the test harness needed some utils from `next-stats-action`
so I moved that into `test/lib/link-packed-packages`.
Node.js ships with a built-in `fetch` now so `node-fetch` is no longer
necessary. Mostly motivated by tracing Node.js deprecation warnings
which originated from `node-fetch` by calling the deprecated
`url.parse`.
Call sites keep working through a compatibility type on `fetchViaHTTP`
that translates node-fetch-only options: Instead of `agent` we pass to
`http(s)` directly, `timeout` becomes `AbortSignal.timeout`, and Node.js
readable streams are accepted as bodies with `duplex: 'half'` set
automatically.
The `abort-controller` polyfill is dropped since its signal type
predates the current AbortSignal and undici would not honor it.
`node-fetch` stays installed because `scripts/generate-release-log.mjs`,
`scripts/reset-project.mjs`, and `scripts/update-google-fonts.js` still
import it (follow-up material). Fixture apps will be migrated
separately.
tsec enforces nothing today. Of its 22 rules, 16 have no callsite in the
four packages it checks, 5 find violations that are all suppressed by
tsec-exemptions.json, and ban-element-innerhtml-assignments has been
inert since TypeScript dropped the `InnerHTML` mixin from lib.dom.d.ts,
so its matcher resolves to no type. Net enforced findings: zero.
font, third-parties and next-playwright never extended
tsconfig-tsec.json, so they ran all 22 rules over source with no DOM
sinks at all, paying a full extra type-check each for nothing.
## Summary
Upgrades the vendored `web-vitals` dependency to v6.0.1 and enables
soft-navigation reporting for CLS, INP, and LCP. This lets
`useReportWebVitals` report metrics against the navigation where they
occurred, including `navigationURL` for route attribution.
The upgrade also removes the obsolete FID integration and compatibility
patch, regenerates the standard and attribution bundles, and updates the
public metric types and documentation to match the current upstream API.
Related to #97161.
## Verification
- Added production-mode coverage for initial and soft-navigation Web
Vitals reporting with both Turbopack and Webpack.
<!-- NEXT_JS_LLM -->
### Why?
Should come up with better solution that does not block PRs with git
conflict
x-ref:
https://vercel.slack.com/archives/C02CDC2ALJH/p1785263902728189?thread_ts=1785263687.502649&cid=C02CDC2ALJH
### How?
- Delete `errors.json`, the error-code SWC plugin, generated WASM, merge
driver, and validation/build tooling.
- Stop attaching error codes to server-rendering digests, redboxes, and
telemetry; native `Error.code` and `Error.name` remain available where
applicable.
- Remove the development-overlay error feedback UI, middleware, and
telemetry event that depended on stable codes.
- Update fixtures, snapshots, and guidance for code-free errors and
numeric-only digests.
<!-- NEXT_JS_LLM -->
Fork PRs run without repository secrets, so deploy tests never run on
external contributions. The workaround is to re-push the contributor's
commits to a branch in `vercel/next.js` and open a replacement PR from
there, which is fiddly by hand and carries a real security risk. This
adds a `pr-adopt` PNPM script to encode the procedure.
```bash
pnpm pr-adopt <pr-number> # adopt
node pr-adopt <pr-number> --dry-run # report without pushing
```
We copy over the title and body as-is and prepend a `Adopts #N. Closes
#N.` line so that the underlying PR automatically closes and we create
the proper backlinks.
Commits are signed
The description is copied rather than rewritten for two reasons: it is
the contributor's own text, and any `Fixes #123` inside it has to
survive, since the original PR closes unmerged and so never fires its
own.
### The confirmation gate
Adoption runs the contributor's code on a branch where CI has repository
secrets, which is exactly what a fork PR is denied. Anything executing
during install, build, or test can read and exfiltrate them, so before
touching git the script prints the PR link, status, author handle and
association, diff size, and every file touched. Confirming requires
retyping the author's handle, and a non-interactive stdin is refused
rather than silently proceeding.
Two details worth a reviewer's attention:
- **The file list is deliberately unranked.** An earlier revision
flagged "high-risk" paths (`.github/`, `package.json`, lockfiles). That
was removed: a payload can sit in any fixture or source file, and
printing "no high-risk paths touched" manufactures false assurance.
- **The list comes from the paginated REST endpoint.** `gh pr view
--json files` silently caps at 100 entries. #96550 has 106 changed files
and returns 100, which would present a truncated list as the complete
surface. If the count still falls short of `changedFiles` (GitHub stops
at 3000), the shortfall is reported rather than hidden.
Draft and closed PRs are both adoptable, since a contributor may still
be iterating or may have abandoned an unreviewed change; the status is
surfaced rather than enforced. Merged PRs are refused because their
commits are already in `canary`.
https://github.com/vercel/next.js/issues/94634#issuecomment-5190067214:
> Module not found: Can't resolve '@swc/helpers/_/_wrap_reg_exp'
> This is on 16.3.0, if that matters. I'm not sure if I understood you
completely, but I want to try your fix before I open a new issue.
That helper was added in March 2026. We had upgraded the swc crates
itself but never upgraded the swc helpers.
Note that https://github.com/advisories/GHSA-6g55-p6wh-862q does not
affect Next.js users unless they build from untrusted source code (which
would have more severe security implications). We're merging this to
reduce noise from security scanners.
- 8.5.21 is 50h+ old so it can be installed
- 8.5.22 is still under 48h - valid at 2026-07-24 08:48:15 UTC
Closes: https://github.com/vercel/next.js/issues/96349
[`httpxy`](https://github.com/unjs/httpxy) is a faster and actively
maintained variant of `http-proxy`. It also doesn't need any manual
patches to avoid security vulnerabilities in Next.js.
`httpxy` is used by Nuxt and maintained by the Nitro team.
This adds `bench/dev-validation/`, wired as `pnpm bench:dev-validation`,
which measures how much dev-mode Cache Components validation contends
for the dev server's event loop during rapid navigation, and how much
running it on a worker thread relieves that. It toggles
`experimental.devValidationWorker` (added in the previous commit) to A/B
the two configurations on the same build. Until the worker
implementation lands the flag is inert and the A/B shows no delta.
The fixture generates one route per family (`client`, `server`,
`sprite`), each nested several layout segments deep under a `(routes)`
route group. Validation renders a combined payload at every URL depth,
so a deeper route means more validation work per navigation, which
mirrors a realistically deep app rather than a single flat segment. The
runner clicks a family's `<Link>` repeatedly, since navigating to the
current route re-renders and re-validates it on every click. The routes
carry no `instant` config because dev validation applies to page
segments by default at the warning level. The three families isolate the
client prerender, the Flight re-encode plus owner-stack work, and the
Flight payload size, respectively.
The signal is browser-observed TTFB taken from Playwright's own network
timing, because it includes the time a request waits for the event loop
while validation monopolizes it. We deliberately do not use the CLI's
logged request durations: the dev server starts that clock inside the
request handler, after the loop has already yielded to the request, so
the queue wait is invisible to it.
The runner prints each configuration's absolute TTFB (p50/p95/max) side
by side rather than a ratio. The time the worker frees is the validation
render's CPU, which is bounded, route-dependent, and does no IO, so a
ratio would overstate a win that does not scale with total request time.
Because the clicks are back-to-back the numbers are a worst case —
navigations that land inside the validation window — and the `max` tail
is the honest headline: it is the main-thread stall the worker removes.
Added a bunch of stuff to the bench.
---
**New metrics in the HTTP benchmark**
- Report TTFB per route (time to first body byte), next to total
latency.
- Report each route's document size, how many bytes are inline Flight
payload, and the Flight share.
**New script: `pnpm bench:render-pipeline:client`**
- Loads each route in Chrome with tracing and 4x CPU throttling, and
breaks down where client time goes: evaluating chunks, evaluating inline
Flight scripts, compiling, background parsing, GC, time to hydration,
and blocking time before hydration.
- Also prints FCP/LCP/DOMContentLoaded/load from the same trace, and JS
transferred vs parsed.
- Off by default, separate from the timing benchmark, since tracing
perturbs timing.
- Hydration time comes from a small client component added to the
fixture root layout that calls `performance.mark`.
**Bug fixes**
- The benchmark was replacing the fixture's `next.config.js` with an
empty one during runs.
- If the port was already taken, the benchmark could silently measure
whatever server was already running there. Both scripts now refuse to
start if something is already on the port.
- A server that died on startup used to look like a slow server; now it
errors immediately.
- One failed request used to abort the whole run and throw away all
results. Now it costs one sample and gets counted in `errors`.
- Killing an already-dead server used to hang the script.
- Bad flags now error upfront instead of crashing at the end.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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 -->
Instead of hardcoding the list, we now automatically detect any
`.browser` sibling and create a compiler alias for that module.
Together with a linting rule, we'll ensure no future regressions are
handled automatically (detection of conditional require and missing
.browser sibling).
This isn't the smoothest DX yet. Ideally we'd generate the list at
Next.js compile time (i.e. before publishing) to avoid having to rerun a
script (I'll follow-up). Generating the list during build time (next dev
or next build) is just another tiny cost users would have to pay.