### 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 -->
The hypothesis is that this is just installing some font stuff that
isn't actually needed for Chromium to work:
```
The following additional packages will be installed:
libnss3-tools xfonts-encodings xfonts-utils
Recommended packages:
fonts-ipafont-mincho fonts-liberation-sans-narrow fonts-tlwg-loma
The following NEW packages will be installed:
fonts-freefont-ttf fonts-ipafont-gothic fonts-liberation fonts-tlwg-loma-otf
fonts-unifont fonts-wqy-zenhei xfonts-cyrillic xfonts-encodings
xfonts-scalable xfonts-utils
The following packages will be upgraded:
libnss3 libnss3-tools
2 upgraded, 10 newly installed, 0 to remove and 35 not upgraded.
```
`apt` is very slow, so skipping this step should significantly reduce
setup time per test shard.
It seems like this isn't always true for other browsers, so only skip
this for Chromium (that's fine, that's what most of our jobs use
anyways).
On 16 core arm runners, "Install Playwright Browsers" typically took
18-24 seconds. Now it takes 5-6s.
Let's estimate that saves about 15s per Linux job that installs
Chromium. Claude estimates that there are 73 chromium-only Linux jobs on
this PR:
<img width="891" height="148" alt="Screenshot 2026-07-06 at 10 30 16 PM"
src="https://github.com/user-attachments/assets/725893fa-9f6f-4806-a3b4-9858ab0ebe66"
/>
So that's 15*73/60=18.25 minutes saved per PR push (the average PR gets
many pushes). At the public (before any volume discounts) price of
$0.026/minute for the 16 core arm runners, that comes out to $0.4745 per
PR push saved.
This workflow has had 3,894 runs in the last 30 days, so that could be
$1,847/mo saved, but many of those workflow runs were canceled, and some
likely failed before getting to these jobs, so the actual savings are
probably somewhere around half of that -- scroll through
https://github.com/vercel/next.js/actions/workflows/.github/workflows/build_and_test.yml
and see that a little less than half of the workflow runs end up
canceled.
### What?
Migrate remaining direct `next-webdriver` test callers that have a
`NextInstance` to `next.browser()`, and expose the shared `Playwright`
browser type from `e2e-utils`.
### Why?
`NextInstance.browser` should be the supported browser-opening interface
for test fixtures, with `next-webdriver` kept as the private
implementation detail.
### How?
Updated affected development, e2e, and production tests to call
`next.browser()` directly, passing `baseUrl` where tests intentionally
target a manually spawned or proxied server. Shared helpers now receive
browser callbacks from the test context, and browser types import
`Playwright` from `e2e-utils` instead of deriving from `next.browser` or
importing from private paths.
<!-- NEXT_JS_LLM_PR -->
## What?
Converts existing `createNext()` usage into `nextTestSetup()`.
`createNext()` was the setup step we had before `nextTestSetup()` was
added.
This PR focused on the simple conversion cases. There will be a
follow-up to complete the last few.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
When SSR fails in development — whether the failing page is in the App Router or the Pages Router — Next.js falls back to rendering the Pages Router `/_error` page and serializes the original error into `__NEXT_DATA__.err`. The client bootstrap in `packages/next/src/client/ index.tsx` later re-throws a fresh `new Error(initialErr.message)` so that the dev overlay picks it up. The error-code SWC plugin stamps that new `Error` with the generic code mapped to `%s` in `errors.json` because the message argument is an identifier, not a statically-known string, which is how the overlay ended up showing the wrong code for errors like `UseCacheTimeoutError`.
The pipeline has two problems on the way to the overlay. First, `errorToJSON` in `packages/next/src/server/render.tsx` only copies the error's standard fields (`name`, `source`, `message`, `stack`, `digest`) into `__NEXT_DATA__.err`, so the real `__NEXT_ERROR_CODE` attached to the thrown error never reaches the client. Second, the subsequent rewrap in `getServerError` at `packages/next/src/server/dev/node-stack- frames.ts` creates yet another `new Error(...)` that the plugin stamps with the same generic code, clobbering anything the caller might have set on the rewrapped instance.
This change plumbs the code through. `errorToJSON` now also emits `__NEXT_ERROR_CODE` using `extractNextErrorCode` so the value survives JSON serialization into `__NEXT_DATA__.err`; the type in `packages/next/src/shared/lib/utils.ts` is updated to match. Both rewrap sites — the client bootstrap and `getServerError` — copy the code from the source error onto the fresh `Error` via `Object.defineProperty` with `enumerable: false` and `configurable: true`, matching how the plugin itself stores the property, which overrides the generic code the plugin stamped on the rewrapped instance. The `use-cache-hanging` e2e test snapshot is updated from `E394` to `E236` now that the real code surfaces in the dev overlay.
### What?
Removes the `experimental.devCacheControlNoCache` config option entirely
and hard-codes `no-cache, must-revalidate` as the dev server
`Cache-Control` header value.
Previously the option controlled whether the dev server responded with:
- `no-store, must-revalidate` (default, `false`)
- `no-cache, must-revalidate` (opt-in, `true`)
This PR first flips the default to `true`, then removes the option
altogether — making `no-cache, must-revalidate` unconditional in all dev
code paths.
### Why?
`no-cache` is strictly better than `no-store` for the dev server:
- `no-cache` allows the browser to revalidate (conditional
`If-None-Match`/`If-Modified-Since` requests), letting the server
respond with `304 Not Modified` when nothing changed → faster page loads
during development.
- `no-store` forces a full re-fetch every time, discarding valid cached
responses.
Since `no-cache` is the correct behavior for all dev users, the toggle
has no remaining value and can be removed to simplify the codebase.
### How?
**Two commits:**
1. `bcec825` — Flip the default from `false` → `true`; update
tests/fixtures/manifest to reflect the new default.
2. `e6e919f` — Remove the option entirely:
- Deleted `devCacheControlNoCache?: boolean` from `ExperimentalConfig`
interface, `config-schema.ts` Zod schema, `defaultConfig`,
`NextConfigRuntime`, and `getNextConfigRuntime()`.
- Replaced all four conditional ternaries in `base-server.ts`,
`router-server.ts`, `pages-handler.ts`, and `app-page.ts` with the
hard-coded string `'no-cache, must-revalidate'`.
- Deleted the `dev-cache-control-no-cache-disabled` test suite (was
testing the `false` path which no longer exists).
- Simplified the remaining `dev-cache-control-no-cache` test (removed
experimental framing).
- Synced `rspack-dev-tests-manifest.json`.
---------
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
`url.parse` is deprecated in favor of WHATWG URLs.
Almost all tests don't actually test integration with `url.parse`.
The remaining places test integration with the Next.js request handler
which does accept `url.parse` values.
We may deprecate that specific signature in favor of accepting WHATWG
URLs instead.
update @next/rspack-core version to 1.0.2 and update the snapshot
other changes:
- packages/next/src/build/webpack-config.ts
Adjusted configuration to account for differences in default node config
between Rspack and Webpack.
- packages/next/src/shared/lib/format-webpack-messages.ts
Added a fallback to moduleIdentifier in cases where Rspack does not
correctly populate moduleName.
1. Fixed the incremental update bug in buildChunkGraph.
2. Fixed a bug in Rspack's built-in CssChunkingPlugin.
For detailed release information, please see
https://github.com/web-infra-dev/rspack/releases.
Note: All the faulty Rspack test cases on GitHub, from what I can see,
either time out or also produce errors in Rspack version 1.5.0.
---------
Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
Enabling `experimental.isolatedDevBuild` required many changes to the
current workflow, so we will incrementally roll out to the tests.
Enabling on test-dev instead of test-experimental-dev because
`-experimental` CIs are filtered via `experimental-tests-manifest.json`
and they don't cover all tests. We want to enable this feature by
default so we should ensure this incremental rollout is covered on all
test cases.
The flag was enabled for `test-experimental-dev` at
https://github.com/vercel/next.js/pull/84099, and this PR moves the flag
to the `test-dev` job.
1. ~~test-experimental-dev
([link](https://github.com/vercel/next.js/pull/84099))~~
2. test-dev (here)
3. test-prod
4. test-integration
5. test-unit
6. Enable by default, remove the flag, and update the rest
x-ref: https://github.com/vercel/next.js/pull/84043
This commit addresses the bugs where Hot Module Replacement (HMR) was
not functioning correctly in Rspack mode. Component edits would often
result in no update or a full page reload instead of a fast,
state-preserving refresh.
This commit implements a new, custom React Refresh plugin and runtime
specifically for Rspack, which correctly orchestrates the HMR lifecycle.
The `legacyBehavior` prop of the `Link` component has been deprecated
since #77473. For Next.js 16, we're finally removing support for it.
Consequently, we're also removing support for the `passHref` prop, which
was only useful in conjunction with the `legacyBehavior` prop.
A [codemod is
available](https://nextjs.org/docs/app/guides/upgrading/codemods#new-link)
to help you automatically upgrade your codebase.
reverts #77473
---------
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
### Improve performance of chunk bootstrapping leveraging arrays and maps
Currently we bootstrap the set of the modules in a chunk by passing an object to the runtime, this is suboptimal for a few reasons:
* The runtime just wants to iterate the object and copy it into a runtime datastructure and iterating objects is relatively slow
* The browser needs to spend time computing the 'shape' of this object and possibly changing internal representations
The solution to both these problems is of course, Arrays!. so instead of an object like
```
{ [moduleId]: (__turbopack_context__) => {},...}
```
we use an array like this
```
[ moduleId, (__turbopack_context__) => {}]
```
* a non-empty sequence of module ids come first
- we may have more than one to support scope hoisted modules
* the factory function
Because these objects are temporary this should make both constructing and iterating them faster.
Finally, in the runtime what we want to do is copy these modules into a global map that is represented as an `Object`. Because we are constantly inserting new keys, this is a suboptimal case and a `Map` should be faster for both insertions and lookups. So this adapts the moduleFactory type to use an es6 `Map` which is more optimized for this particular usecase.
Ideally we would do the same for the moduleCache but that would complicate `require.cache` usecases. We could perhaps leverage a `Proxy` to maintain support but this is left as future work.
### Aside: why are arrays faster than objects?
Arrays are faster to parse because they incur fewer 'shape transitions' (background reading: https://v8.dev/blog/fast-properties vs https://v8.dev/blog/elements-kinds). Basically for every new property v8 parses on the object literal it changes the hidden type (or 'shape'). For arrays the same thing happens but the process is much simpler as there is a fixed set of possible shapes and the browser will hit the terminal one after evaluating the second element.
Also arrays are simply faster to iterate than objects (but this is generally a non-controversial statement).
### Performance
Based on the `module-cost` benchmark this is a consistent win in all scenarios
| Scenario | Metric | HEAD (ms) | CHANGE (ms) | Delta (ms) | % Change |
|----------|--------|-----------|-------------|------------|----------|
| **Pages API ESM** | Load Duration | 33.46 | 27.87 | -5.59 | -16.7% ✅ |
| | Execution Duration | 63.22 | 62.54 | -0.68 | -1.1% ✅ |
| **Pages API CommonJS** | Load Duration | 44.11 | 33.76 | -10.35 | -23.5% ✅ |
| | Execution Duration | 32.05 | 25.70 | -6.35 | -19.8% ✅ |
| **Client ESM** | Load Duration | 34.74 | 27.65 | -7.09 | -20.4% ✅ |
| | Execution Duration | 46.13 | 40.35 | -5.78 | -12.5% ✅ |
| **Client CommonJS** | Load Duration | 30.43 | 24.48 | -5.95 | -19.6% ✅ |
| | Execution Duration | 25.15 | 17.18 | -7.97 | -31.7% ✅ |
### Risks
The biggest risk of this change is subtle bugs introduced due to `string` vs `number` confusion. I found one subtle issue in how we rewrite `new Worker()` constructors, there could be more not covered by tests.
### What?
In development give the module factory a useful name, so it's clear in stack traces that this is the module evaluation part of the execution.
This also fixes some bugs with the stack trace parser, which seems to struggle if function names contain brackets. The automatically inferred function name would be the module id, which contains brackets.
When we updated `experimental.strictNextHead` to be true in #65418, we
did not update all spots that would default to true in the case where
the value was omitted entirely.
This led to the default value not being correctly applied in pages
router, which resulted in duplicate metadata w/ React 19.
Since we made the flag the default, we can also probably clean up this
flag all together, but that can be done separately.
Fixes#81655Fixes#81689
webpack-only at the moment since I have no easy stack to test this on in Turbopack until we sourcemap the Turbopack runtime.
This applies the same heuristic Chrome uses. For sources whose associated sourcemap ignores everyone of its sources, we ignore-list that source even if we didn't find a mapping. This is similar to other common heuristic where the closest mapping is used when no exact mapping exists.
Jest can run different suites in parallel, but tests within the same suite must run sequentially in the same worker.
You don't want suites that are too small, because each suite requires relatively expensive repository setup, but you also don't want suites that are too large because it inhibits parallelism.
This is a big problem for arewerspackyet, where many of these test cases are currently failing. Each test can take up to 4 minutes to time out, so we easily time out the entire 90 minute long test runner job.
## Test plan
Tested by running all these tests in parallel with:
```
NEXT_TEST_MODE=dev HEADLESS=true node_modules/.bin/jest test/development/pages-dir/client-navigation
```
It completes in about 2m40s minutes on my machine.
- `newDevOverlay: true` by default (enables experimental React builds on
canary until owner stacks progress further)
- `run-tests` now sets the env var for tests that were relying on it for
forking behavior
- PPR runners now run with the flag disabled to help catch regressions
in the old overlay until we remove it
- Fixed a number of tests that had outdated snapshots or missed forking
behavior because they weren't running in CI
- Disabled a test that was failing in Turbopack + Experimental React
that is unrelated to the overlay (see:
https://github.com/vercel/next.js/pull/75989)
---------
Co-authored-by: devjiwonchoi <devjiwonchoi@gmail.com>
- Before, there was a separate AST traversal that collected `next-dynamic` imports
- Now, we use transitions (which are inserted by the existing next-custom-transform) to tell Turbopack about the next-dynamic imports
- Unlike Webpack (where the react-loadable identifiers are `path/to/parent-module -> ./imported-client`), Turbopack just uses the module id: `[project]/path/to/imported-client.js [app-client] (ecmascript, next/dynamic entry)`
- the `next-dynamic` transition only inserts the marker module (i.e. for the SSR `import()`)
- the `next-dynamic-client` transition inserts the marker module and changes the module context (used to get the client module name)
- Now, we use the correct `AvailabilityInfo` for the next/dynamic chunks to preload, and they are not explicitly emitted. If they are for some reason not the same chunks that are actually emitted for the dynamic import, you get a hard error. (Previously, they didn't use the same `AvailabilityInfo` as the real async chunks, so they usually bundled another copy of React, and also had a different chunk name hash)
- For Turbopack, react-loadable manifests are now scoped per page instead of globally
Pages router (`/pages`) will continue to support React 18 not the React
19 RC. Current thinking is that we'll add support for React 19 in Pages
Router once 19 is stable.
This does not affect App Router (`/app`) which continues to use the
latest React Canary (i.e. React 19).
https://github.com/vercel/next.js/pull/65058 is required reading to
understand the changes in this PR
---------
Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
...with `assertHasRedbox` and `assertNoRedbox`.
`hasRedbox()` has a hardcoded timeout of 5s that is only required for
the negative assertion.
Instead, we now have dedicated assertions for the positive
(`assertHasRedbox`) and negative case (`assertNoRedbox`).
The negative assertion still has the hardcoded timeout.
But the positive assertion just retries until we find the Redbox.
This speeds up tests using the positive assertion.
Removing `hasRedbox` also uncovered some unused expressions e.g. `await
hasRedbox(browser)`.
These expressions probably wanted to use `expect(await
hasRedbox(browser)).toBe(true)