The depth-based instant validation previously only iterated
URL-consuming segments as potential shared/new boundaries. Route groups
between URL segments were invisible — when a route group layout with
Suspense was shared in a client navigation, its Suspense appeared to
cover blocking code in the validation render even though it wouldn't in
reality.
This adds a second dimension to the validation iteration: group depth.
At each URL depth, we now also step through route group boundaries. A
new `discoverValidationDepths` function walks the LoaderTree and returns
an array where each entry represents the max group depth at that URL
depth. The validation loop iterates from deepest group depth to
shallowest within each URL depth, stopping on the first error.
Key design decisions:
- URL depth and group depth counters are advanced before the boundary
check, making the boundary condition a simple `nextUrlDepth > depth &&
currentGroupDepth >= groupDepth`
- The synthetic `(slot)` segment that Next.js inserts for parallel slots
is excluded from group depth counting since it doesn't represent a real
navigation boundary
- Group depths are discovered from the LoaderTree rather than the URL
pathname, making the tree the source of truth for validation bounds
This improves the blocked-request warning so it names the actual dev
resource being requested and gives clearer guidance on how to allow it.
When the source host is known, the message includes an inline
`allowedDevOrigins` config snippet; when the source is missing or
opaque, it explains why Next.js cannot infer a host to allow.
- More profiles means more bloat to the `target` directory and more cold builds.
- There's no practical use case where we'd be okay with the performance hit of `debug-assertions` and not okay with the performance hit of `lto = false`. Neither profile was suitable for benchmarking.
- `release-with-assertions-no-lto` is too long to type and was giving me carpal tunnel.
This removes the warn-only default behavior and enforces the dev-origin guard by default. Cross-origin requests to internal dev resources now block unless they match the built-in local allowlist or an explicit `allowedDevOrigins` entry. The tests are expanded to cover default blocking, configured-but-not-allowlisted hosts, missing Referer in the no-cors path, and same-site requests without an Origin, and the docs are updated to match the new behavior.
## Move `adapterPath` from experimental to stable
Graduates `adapterPath` from `experimental.adapterPath` to a top-level
`NextConfig` option, following the same pattern used for
`cacheHandlers`, `cacheComponents`, `typedRoutes`, and other options
that moved out of experimental.
### Changes
- **Type definition** (`config-shared.ts`): Added `adapterPath` to
`NextConfig` interface, marked `ExperimentalConfig.adapterPath` as
`@deprecated`, moved default value to top-level in `defaultConfig`,
updated `NextConfigRuntime` and `getNextConfigRuntime`
- **Zod schema** (`config-schema.ts`): Added `adapterPath` to top-level
`configSchema`
- **Backward compat** (`config.ts`): Added
`warnOptionHasBeenMovedOutOfExperimental` call so existing
`experimental.adapterPath` configs still work with a deprecation
warning, updated `applyModifyConfig` to read from `config.adapterPath`
- **Runtime references**: Updated `build/index.ts`, `app-page.ts`,
`app-route.ts`, `pages-handler.ts` to use `config.adapterPath` /
`nextConfig.adapterPath`
- **Tests**: Updated 4 test fixture `next.config` files
- **Docs**: Updated app router doc (removed `version: experimental`
frontmatter, top-level config example), pages router doc title, and v16
upgrade guide
### What?
Remove the `TURBOPACK_STATS` feature that generated webpack-compatible
`webpack-stats.json` files from Turbopack's output.
**Deleted:**
- `crates/next-api/src/webpack_stats.rs` — the entire 305-line Rust
implementation that built webpack-format stats (assets, chunks, modules,
entrypoints) from Turbopack's module graph
**Rust changes:**
- `crates/next-api/src/lib.rs` — removed `mod webpack_stats;`
- `crates/next-api/src/project.rs` — removed
`should_create_webpack_stats()` which read the `TURBOPACK_STATS` env var
- `crates/next-api/src/pages.rs` — removed `generate_webpack_stats`
import and stats output emission
- `crates/next-api/src/app.rs` — same
**TypeScript changes:**
- `packages/next/src/shared/lib/constants.ts` — removed `WEBPACK_STATS =
'webpack-stats.json'` constant
- `packages/next/src/shared/lib/turbopack/manifest-loader.ts` — removed
`loadWebpackStats()`, `writeWebpackStats()`, `mergeWebpackStats()`, the
`webpackStats` field, webpack type imports, and the `TURBOPACK_STATS`
env guard in `writeManifests()`
- `packages/next/src/server/dev/turbopack-utils.ts` — removed
`shouldCreateWebpackStats` flag and two `loadWebpackStats()` call sites
- `packages/next/src/build/handle-entrypoints.ts` — same
### Why?
This feature is no longer needed and adds dead complexity. It was opt-in
via `TURBOPACK_STATS=1` but nothing in the current codebase or tooling
depends on it.
### How?
Pure deletion — no behavior change for any existing user, as the feature
was opt-in via an environment variable that is no longer checked.
**Intentionally left untouched:**
- Webpack's own `stats.json` generation (non-Turbopack path)
- The Turbopack bundle analyzer / `analyze` feature
- `route-bundle-stats.json` in `.next/diagnostics/`
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
This PR makes configured `allowedDevOrigins` apply to cross-site no-cors dev asset requests. When browsers omit Origin for subresource loads, the dev guard now falls back to `Referer` so explicit allowlisted hosts can load `/_next/*` resources in development.
Previously, when `allowedDevOrigins` was configured, cross-site no-cors requests to internal Next.js dev resources were still blocked even for allowlisted hosts, because that code path never consulted the allowlist.
### 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>
**Previous:**
1. https://github.com/vercel/next.js/pull/91487
2. https://github.com/vercel/next.js/pull/91488
**Current:**
3. https://github.com/vercel/next.js/pull/89297
---
Most of the vary params infrastructure was already implemented in
previous PRs for static prerenders. This wires up the remaining pieces
for runtime prefetches — creating the accumulator, setting it on the
prerender store, and resolving it before abort — and adds additional
test cases covering empty/full vary sets, searchParams, metadata, and
per-segment layout/page splits with runtime prefetching.
**Previous:**
1. https://github.com/vercel/next.js/pull/91487
**Current:**
2. https://github.com/vercel/next.js/pull/91488
**Up next:**
3. https://github.com/vercel/next.js/pull/89297
---
When a prefetch response includes vary params, the segment cache rekeys
entries to a more generic path based on which params the segment
actually depends on. Previously, the rekeying only happened when vary
params were provided. Now that vary params are tracked for more response
types (and eventually will always be tracked), entries are rekeyed in
more cases than before.
This exposed a potential race condition: the scheduler would capture a
vary path at scheduling time and upsert the entry at that path when the
fetch completed. But the fetch functions themselves rekey entries to a
different (more generic) path upon fulfillment. The deferred upsert
could then move the entry back to the less generic path, undoing the
rekeying.
To fix this, move the upsert logic inline into the fetch functions that
fulfill entries, rather than deferring it to an external callback. This
removes the race condition, simplifies the model, and reduces
implementation complexity. The previous structure existed to avoid the
rekeying cost when vary params weren't available, but rekeying is
inexpensive and not worth the added indirection.
The upsert function itself already handles concurrent writes by
comparing fetch strategies and checking whether the new entry provides
more complete data than any existing entry. So it's safe to always call
it inline — whichever entry wins will be the most complete one.
**Current:**
1. https://github.com/vercel/next.js/pull/91487
**Up next:**
2. https://github.com/vercel/next.js/pull/91488
3. https://github.com/vercel/next.js/pull/89297
---
Prefetch responses include metadata (in the Flight stream sense, not
HTML document metadata) that describes properties of the overall
response — things like the stale time and the set of params that were
accessed during rendering. Conceptually these are like late HTTP
headers: information that's only known once the response is complete.
Since we can't rely on actual HTTP late headers being supported
everywhere, we encode this metadata in the body of the Flight response.
The mechanism works by including an unresolved thenable in the Flight
payload, then resolving it just before closing the stream. On the
client, after the stream is fully received, we unwrap the thenable
synchronously. This synchronous unwrap relies on the assumption that the
server resolved the thenable before closing the stream.
The server already buffers prefetch responses before sending them, so
the resolved thenable data is always present in the response. However,
HTTP chunking in the browser layer can introduce taskiness when
processing the response, which could prevent Flight from decoding the
full payload synchronously. The existing code includes fallback behavior
for this case (e.g. treating the vary params as unknown), so this
doesn't fix a semantic issue — it strengthens the guarantee so that the
fallback path is never reached.
To do this, we buffer the full response on the client and concatenate it
into a single chunk before passing it to Flight. A single chunk is
necessary because Flight's `processBinaryChunk` processes all rows
synchronously within one call. Multiple chunks would not be sufficient
even if pre-enqueued: the `await` continuation from
`createFromReadableStream` can interleave between chunks, causing
promise value rows to be processed after the root model initializes,
which leaves thenables in a pending state. Since the server already
buffers these responses and they complete during a prefetch (not during
a navigation), this is not a performance consideration.
Full (dynamic) prefetches are not affected by this change. These are
streaming responses — even though they are cached, they are a special
case where dynamic data is treated as if it were cached. They don't need
to be buffered on either the server or the client the way normal cached
responses are.
Follow-up to #91189 which added support for accessing root params inside
`generateStaticParams`. Previously, if a `generateStaticParams` function
tried to read a root param that hasn't been defined yet — either because
the current segment is supposed to define it, or because a child segment
defines it — `getRootParam` would silently return `undefined`. This is
especially problematic because #91019 will type root param getters as
`Promise<string>` without `undefined`, making this a silent type safety
violation.
This change makes `getRootParam` throw an explicit error with the
`generate-static-params` work unit store when the requested param is not
present in `rootParams`.
When a `"use cache"` entry is newly generated during a prerender
(`prerender` or `prerender-runtime`), `collectResult` defers propagation
of cache life and tags to the outer context. This is because the entry
might later be omitted from the final prerender due to short expire or
stale times, and omitted entries should not affect the prerender's cache
life.
However, when a cache handler returns a hit for an existing entry,
`propagateCacheEntryMetadata` was called unconditionally, without the
same deferral logic. This meant that short-lived cache entries retrieved
from the cache handler could propagate their cache life to the prerender
store, even though they would later be omitted from the final render.
This inconsistency is currently not observable because runtime
prefetches use a prospective and final two-store architecture (see
`prospectiveRuntimeServerPrerender` and `finalRuntimeServerPrerender` in
`app-render.tsx`). The cache handler hit propagation corrupts the
prospective store, but the response is produced from the final store,
which reads from the resume data cache with correct stale and expire
checks. Static prerenders have a similar two-phase architecture that
masks the issue. Because of this, there is no test case that can observe
the incorrect behavior, but the fix avoids confusion and prevents the
inconsistency from becoming a real bug if the architecture changes.
This change extracts a `maybePropagateCacheEntryMetadata` function that
encapsulates the conditional propagation logic and is now called from
both the generation path (inside `collectResult`) and the cache handler
hit path. The resume data cache read path continues to call
`propagateCacheEntryMetadata` unconditionally, since it runs in the
final render phase after short-lived entries have already been filtered
out.
Root params (e.g. `import { lang } from 'next/root-params'`) can now be read inside `"use cache"` functions. The read root param values are automatically included in the cache key so that different root param combinations produce separate cache entries.
Since which root params a cache function reads is only known after execution, the cache key is reconciled post-generation. When root params are read, a two-key scheme is used: the full entry is stored under a specific key (coarse key + root param suffix), and a lightweight redirect entry is stored under the coarse key. The redirect entry's tags encode the root param names using the pattern `_N_RP_<rootParamName>` (e.g. `_N_RP_lang`), following the convention of existing internal tags like `_N_T_<pathname>` (e.g. `_N_T_/dashboard`) for implicit route tags. This allows a cold server to resolve the specific key on the first request after restart. An in-memory map (`knownRootParamsByFunctionId`) provides a fast path for subsequent invocations. When no root params are read, the full entry is stored directly under the coarse key with no redirect involved.
The in-memory map grows monotonically — if a function conditionally reads different root params across invocations, the set accumulates all observed param names. The redirect entry's tags are built from this combined set, ensuring cold servers always resolve the most complete specific key.
The two-key scheme only applies to the cache handler. The Resume Data Cache (RDC) always uses the coarse key because each page gets its own isolated RDC instance, so root params are fixed within a single RDC and no disambiguation is needed. When an RDC entry is found during resume, it seeds `knownRootParamsByFunctionId` so that subsequent cache handler lookups can use the specific key directly.
Reading root params inside `unstable_cache` still throws. Reading root params inside `"use cache"` nested within `unstable_cache` throws with a specific error message explaining the limitation.
Alternatives considered: extending the `CacheEntry` interface (would be a breaking change for custom cache handlers), encoding root param metadata in the stream via a wrapper object, or sentinel byte, or Flightception (runtime overhead of stream manipulation on every cache read), and deferring `cacheHandler.set` until after generation (breaks the cache handler's pending-set deduplication for concurrent requests).
### Why?
TypeScript fails to infer the generic `P` from `Omit<P, 'children'>` in
the fallback function parameter.
```
Argument of type '(props: { title: string; }, { error, reset, unstable_retry }: ErrorInfo) => Element' is not assignable to parameter of type '(props: Omit<UserProps, "children">, errorInfo: ErrorInfo) => ReactNode'.
Types of parameters 'props' and 'props' are incompatible.
Property 'title' is missing in type 'Omit<UserProps, "children">' but required in type '{ title: string; }'.ts(2345)
catch-error-wrapper.tsx(7, 12): 'title' is declared here.
```
Startup loading of APP_PAGE PPR entries seeded the in-memory cache from
disk using a size estimate that only counted html and the route-level
RSC payload. Empty-shell prerenders have 0-byte html and do not persist
a monolithic .rsc file, with the reusable data instead stored in
postponed metadata and per-segment .segment.rsc files. That made the
computed size 0, caused LRUCache to reject the seeded entry, and forced
the first request after next start to rerun prerender and rewrite the
build artifacts.
Account for postponed state and segment buffers when sizing APP_PAGE
cache entries so empty-shell prerenders can warm the startup cache the
same way contentful shells do. Add a production regression that asserts
both routes are emitted as build artifacts and that next start does not
rewrite them on the first request.
The regression skips deployment mode because it verifies self-hosted
behavior by inspecting local .next artifact mtimes, which is not a
portable signal in deployment mode. When deployed through an adapter it
is not expected that the local Next.js process is handling entries
through the Response cache reading from the `.next` folder anyway
This gets `cargo doc` clean, and enables it in CI so that we won't regress it (I'm open to removing this if it proves to be annoying).
I also cleaned up some documentation and even found some dead code (deleted `turbopack/crates/turbopack-dev-server/src/source/conditional.rs`).
This enables server HMR (formally, this is called server fast refresh)
by default for app router pages. For now, it's still disabled for routes
and middleware even with app router.
- Replaces `--experimental-server-fast-refresh` with
`--no-server-fast-refresh` to opt out
- Updates telemetry to record `feature.serverFastRefreshDisabled` when
the disable flag is passed
- [x] Verify `next dev` enables server HMR by default (Turbopack, app
dir, non-edge routes)
- [x] Verify `next dev --no-server-fast-refresh` disables it
- CI
This PR adds `unstable_catchError()` API for a granular custom error
boundary. The error component generated by this API is not a true
component format, as the wrapper provides additional args. Therefore,
the API is a function call by design rather than a boundary component to
avoid merging the `errorInfo` with the user-provided props from the
wrapper.
This API works for both the App/Pages Router. However, `retry()` is not
allowed in Pages Router as it depends on `router.refresh()`, and will
throw. Also, it's exported from `next/error` as there was already an
`Error` component for the [Pages
Router](https://nextjs.org/docs/pages/building-your-application/routing/custom-error#reusing-the-built-in-error-page).
### DevTools
<img width="508" height="90" alt="CleanShot 2026-03-13 at 03 02 46@2x"
src="https://github.com/user-attachments/assets/8b79b1f6-877d-4901-99f7-6a2b4d3e34fe"
/>
Docs to follow up: https://github.com/vercel/next.js/pull/89847
Closes NAR-768
---------
Co-authored-by: Josh Story <story@hey.com>
## Background
Next.js has an existing global config,
`experimental.staleTimes.dynamic`, that controls how long the
client-side router cache retains data from dynamic navigation responses.
By default this is 0 — meaning dynamic data is always refetched on every
navigation. Some users set this to a higher value (e.g. 30 seconds) so
that navigating back to a recently visited page reuses the cached
response instead of making a new request.
The limitation of the global config is that it applies uniformly to
every page in the app. Users have asked for the ability to set different
stale times for different pages — for example, a dashboard page that
should always show fresh data vs. a product listing page where it's
acceptable to show slightly stale data for a smoother navigation
experience.
## What this PR adds
`export const unstable_dynamicStaleTime` is a per-page version of
`staleTimes.dynamic`. It accepts a number in seconds with the same unit
and semantics, but scoped to a single page. When present, it overrides
the global config for that page — whether the per-page value is larger
or smaller than the global default. Static and cached responses are
unaffected.
When parallel routes render multiple pages in a single response, the
minimum value across all parallel slots wins, consistent with how stale
times compose generally.
Exporting this from a layout is a build error (pages only).
## Relationship to Cache Components
The recommended way to control data freshness in Next.js is to use Cache
Components (`"use cache"`) with `cacheLife`. This API exists as a
simpler migration path for apps that are already using
`staleTimes.dynamic` and haven't yet adopted Cache Components. It's
prefixed with `unstable_` not because the implementation is unstable,
but because it's not the recommended long-term idiom — apps should
migrate to Cache Components for more granular and composable control
over data freshness.
Based on the design from #88609.
Co-authored-by: Jimmy Lai <laijimmy0@gmail.com>
Co-authored-by: Sam Selikoff <sam.selikoff@gmail.com>
The `outerWorkUnitStore` in the public cache context could previously be `undefined`, which was needed when `"use cache"` was called without a `WorkUnitStore` (e.g. inside `generateStaticParams`) and during background revalidation of stale cache entries (SWR). With the previous commit providing a `GenerateStaticParamsStore` for `generateStaticParams`, the only remaining source of `undefined` was the SWR background revalidation path, which intentionally discarded the store to prevent cache life and tags from propagating back to the outer scope.
A `skipPropagation` flag was added to `CacheContext` to decouple the propagation concern from the store reference. The SWR background regen now passes `skipPropagation: true` while keeping the outer store intact for reads (e.g. `implicitTags`). Previously, nested `"use cache"` calls during SWR regen would pass empty `softTags` to `cacheHandler.get()` because the store was `undefined`. Now they correctly receive the page's implicit tags.
With `| undefined` removed from `PublicCacheContext.outerWorkUnitStore`, all unnecessary existence checks (`?.`, `if (x)`, `case undefined:`, ternaries) were cleaned up throughout `use-cache-wrapper.ts`, and the `shouldForceRevalidate` and `shouldDiscardCacheEntry` signatures were tightened from `WorkUnitStore | undefined` to `WorkUnitStore`.
Note that module-scope `"use cache"` usage (calling a cached function at the top level of a module) was already prevented before this change by the `WorkStore` check, which throws if no `WorkStore` is present. The new `WorkUnitStore` check is an additional guard that comes after it.
This also unblocks a future change to support root params inside `"use cache"` with cache keying. Since the outer work unit store is now always defined, `rootParams` can be threaded from the outer store into the cache store. The only exception will be `UnstableCacheStore`, which doesn't carry `rootParams` and can't include them in its cache key.
A new `use-cache-swr` test suite was added with a persistent cache handler that does not drop entries at revalidate time (unlike the default in-memory handler), which enables testing the server-side SWR code path that was previously uncovered.
> [!TIP]
> Best reviewed with hidden whitespace changes.
A new `GenerateStaticParamsStore` work unit store type is now provided during `generateStaticParams` execution. This enables root param getters (`import { lang } from 'next/root-params'`) to be called inside `generateStaticParams`, allowing shared helpers that internally access root params via the special import to be used in both Server Components and `generateStaticParams` without manually threading params.
Each `generateStaticParams` call now runs within a `workUnitAsyncStorage.run()` context carrying a `GenerateStaticParamsStore` with the correct `rootParams` (extracted from `parentParams` using the already-available `rootParamKeys`). The store extends `CommonWorkUnitStore`, providing `phase` and `implicitTags` which are not strictly necessary but convenient to keep call sites simple.
Request-time APIs (`headers()`, `cookies()`, `connection()`, `draftMode()`) now throw specific errors when called inside `generateStaticParams` instead of the previous generic "called outside a request scope" message. Framework-internal functions like `createSearchParamsFromClient` and `createParamsFromClient` throw `InvariantError` since they should never be reached in this context.
This change also unblocks a follow-up PR that removes `| undefined` from `PublicCacheContext.outerWorkUnitStore` in the use cache wrapper, since `"use cache"` is already supported inside `generateStaticParams` today but previously ran without a `WorkUnitStore`. With this store in place, requiring a `WorkUnitStore` in `"use cache"` won't break that existing usage.
```
// This is only used by Webpack to correctly output the manifest. It's value shouldn't be relied
// upon externally. It's possible that the same action can be in different layers in a single
// page, which cannot be modelled with this API anyway.
```
## What?
When a webpack/turbopack loader produces broken code, error messages now
display **both** the original source and the generated code with source
map information, making it much easier to debug loader issues.
## Why?
Previously, when loaders returned invalid code, error messages only
showed the original source file (after source-map remapping). Users had
no way to see what the loader actually generated, making it hard to
diagnose why the code failed to parse. Showing both sides gives full
context about what went wrong.
## How?
### Turbopack Core (`turbopack-core`)
- **`Source::description()`** — New method on the `Source` trait
providing human-readable descriptions of where code comes from.
Implemented across all source types (`FileSource`, `VirtualSource`,
`WebpackLoadersProcessedAsset`, `PostCssTransformedAsset`, etc.),
producing chains like `"loaders [sass-loader] transform of file content
of ./styles.scss"`.
- **`AdditionalIssueSource`** — New struct to hold a labeled source
location. The `Issue` trait gains an `additional_sources()` method so
issues can expose supplementary code frames.
- **`GeneratedCodeSource`** — A wrapper that strips `GenerateSourceMap`
support from a source, ensuring the *generated* code is displayed as-is
rather than being remapped back to the original.
- **`IssueSource::to_generated_code_source()`** — Helper that detects
sources implementing `GenerateSourceMap` and wraps them in
`GeneratedCodeSource` for display. Used by `AnalyzeIssue` and
`ParsingIssue` to automatically attach generated code frames.
### Error Formatting
- **`turbopack-cli-utils`** — Renders additional sources in CLI issue
output.
- **`format-issue.ts`** — Renders additional sources in the browser
error overlay. Extracted `formatSourceCodeFrame()` helper to deduplicate
code-frame rendering between primary and additional sources.
- Long-line truncation (e.g. minified CSS from SCSS) is handled natively
by the Rust-based `codeFrameColumns` implementation.
### Type Definitions
- Added `SourcePosition`, `IssueSource`, and `AdditionalIssueSource`
interfaces to TypeScript types.
- Updated `PlainSource` (added `file_path`), `PlainIssue` (added
`additional_sources`), and NAPI bindings to pass the data through.
### Test Coverage
- **E2e tests** (`test/e2e/webpack-loader-parse-error/`) with custom
broken JS and CSS loaders, covering all 4 modes:
- **Development (Turbopack)** — Verifies parse errors show both original
and generated code via browser error overlay
- **Development (Webpack)** — Verifies error overlay shows the parse
error (webpack doesn't support additional sources)
- **Production (Turbopack)** — Verifies build failure output with full
error extraction and inline snapshots
- **Production (Webpack)** — Verifies build failure output with inline
snapshots
- Updated `test/development/sass-error/` snapshot to include the new
generated code frame for minified SCSS output.
### Example Output
When a loader produces broken code, users now see:
```
⨯ ./app/data.broken.js:3:1
Parsing ecmascript source code failed
1 | // This file will be processed by broken-js-loader
2 | // The loader will return invalid JavaScript with a source map
> 3 | export default function Data() {
| ^
4 | return <div>original source content</div>
5 | }
6 |
Expected '</', got '{'
Generated code of loaders [./broken-js-loader.js] transform of file content of app/data.broken.js:
./app/data.broken.js:3:46
1 | // Generated by broken-js-loader
2 | export default function Page() {
> 3 | return <div>this is intentionally broken {{{ invalid jsx
| ^
4 | }
5 |
Import trace:
Server Component:
./app/data.broken.js
./app/page.js
```
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <lukeisandberg@gmail.com>
### What?
Fixes the "Ready in" printout for the Next.js dev server after restarts.
### Why?
When the dev server restarts (e.g., due to `next.config` changes), the
"Ready in" time was incorrect, showing the total process uptime instead
of the actual startup duration for the newly restarted server instance.
This was because `NEXT_PRIVATE_START_TIME` was not reset.
### How?
`process.env.NEXT_PRIVATE_START_TIME` is now updated to
`Date.now().toString()` in `packages/next/src/cli/next-dev.ts` right
before `startServer()` is re-called when handling a `RESTART_EXIT_CODE`.
This ensures the "Ready in" time accurately reflects the startup
duration from the point of restart.
The tricky part has been writing a test, that both catches a regression,
and won't be flaky on CI. After some back and forth, I think the Vercel
bot suggestion is probably the simplest.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Joseph Chamochumbi <joseph.chamochumbi@vercel.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: icyJoseph <sephxd1234@gmail.com>
### What?
Two fixes for the Turbopack build tracing introduced in #90397:
1. **Don't block SSG on Turbopack shutdown**: `workerMain()` no longer awaits the shutdown promise before returning. Trace event collection is deferred to `waitForShutdown()`, which the parent process awaits *after* SSG completes. This allows static generation and Turbopack persistence/cache-flush to run in parallel.
2. **Add persistence spans to `trace-build` allowlist**: `turbopack-build-events`, `turbopack-persistence`, and `turbopack-compaction` are now included in the `to-json-build.ts` allowlist so they appear in `.next/trace-build`.
### Why?
- The `await shutdownPromise` in `workerMain()` was too eager — it prevented the caller from acknowledging the build as complete and starting SSG until Turbopack persistence finished flushing to disk.
- The persistence/compaction spans emitted by Rust (`turbopack-persistence`, `turbopack-compaction`) were not in the `to-json-build.ts` allowlist, so they were silently filtered out of `.next/trace-build`.
### How?
**`impl.ts` (worker)**:
- Removed `await shutdownPromise` from `workerMain()` — it now returns build results immediately
- `waitForShutdown()` now returns `{ debugTraceEvents }` after awaiting shutdown, so trace events are collected only after all compilation events (including persistence spans) have been processed
**`index.ts` (parent)**:
- Moved `recordTraceEvents(debugTraceEvents)` from the `workerMain` result handler into the `shutdownPromise` `.then()` chain, so events are replayed into the parent reporter after shutdown completes
**`to-json-build.ts`**:
- Added `turbopack-build-events`, `turbopack-persistence`, `turbopack-compaction` to the allowlist
**Test updates**:
- Enabled `turbopackFileSystemCacheForBuild: true` in the trace-build test fixture
- Updated the Turbopack inline snapshot to include `turbopack-build-events`
## What
Fixes the `test cargo unit` CI job which has been consistently timing out (30 min) on canary since #91343 landed on Mar 14.
## Why
Two issues were contributing to the timeout:
1. **LTO on unit test binaries**: #91343 added `lto = "thin"` to `[profile.release]`. The `release-with-assertions` profile (used by `test-cargo-unit`) inherits from `release`, so it inherited LTO. Unlike the native binary build which only links one artifact, `cargo nextest run --workspace` builds test binaries for dozens of crates — each requiring its own LTO link pass. This pushed the build from ~8 minutes to 30+ minutes, exceeding the job timeout.
2. **No remote cache timeout or streaming output**: The `turbo run test-cargo-unit` command had no `--remote-cache-timeout` (unlike the build step which uses `--remote-cache-timeout 60`) and used turbo's default grouped log output, which buffers all output until the task completes. This made the job appear completely silent for 30 minutes before being killed, making it impossible to diagnose.
## How
- Added a `release-with-assertions-no-lto` cargo profile that inherits from `release-with-assertions` but disables LTO, and switched `test-cargo-unit` to use it. The production native build continues to use `release-with-assertions` with LTO.
- Added `--remote-cache-timeout 60 --log-order stream` to the turbo command for `test-cargo-unit` for parity with the build step and real-time output visibility.
Replaces the segment-path-matching scroll system with a simpler model
based on a shared mutable ScrollRef on CacheNode.
The old system accumulated segment paths during navigation and matched
them in layout-router to decide which segments should scroll. This was
necessary when CacheNodes were created lazily during render. Now that we
construct the entire CacheNode tree immediately upon navigation, we can
assign a shared ScrollRef directly to each new leaf node. When any
segment scrolls, it flips the ref to false, preventing other segments
from also scrolling. This removes all the segment path accumulation and
matching logic.
Fixes a regression where calling `refresh()` from a server action
scrolled the page to the top. The old system had a semantic gap between
`null` (no segments) and `[]` (scroll everything) — a server action
refresh with no new segments fell through to a path that scrolled
unconditionally. The new model avoids this: refresh creates no new
CacheNodes, so no ScrollRef is assigned, and nothing scrolls.
Repro: https://github.com/stipsan/nextjs-refresh-regression-repro
There is extensive existing test coverage for scroll restoration
behavior. This adds one additional test for the server action refresh
bug.
In #91158, I wired up the plumbing to indicate whether a partial
fallback shell is upgradeable. The previous heuristic was too coarse.
This wires up the handling in `next start`.
This PR teaches the runtime to complete a fallback shell only as far as
prerendering allows. Instead of promoting a generic shell like
`/[one]/[two]` directly to `/c/foo`, it promotes it to the most specific
prerendered shell for that branch, `/c/[two]`, where `[one]` has
`generateStaticParams`. That means later requests can reuse the more
complete shell, while fully dynamic params continue to stream as dynamic
content.
With this change:
- the first request still serves the generic fallback shell immediately
- the background revalidate writes a more specific shell, not a fully
concrete route
- later requests for the same prerendered branch reuse that shell
- upgrading stops once only fully dynamic params remain
This keeps the runtime behavior aligned with the build-time
`remainingPrerenderableParams` contract from the downstack PR.
Partial fallback upgrading is currently too broad. Any PPR fallback shell can be treated as upgradeable, including routes with no `generateStaticParams` and shells that are already as specific as prerendering can make them.
In practice, that means we preserve `allowQuery` and enable background promotion for shells that should remain as partial fallbacks. That broadens the feature beyond the medium-cardinality case it was meant for and makes the build output imply per-param upgradeability when there are no prerenderable params left to fill.
This PR teaches static-path generation to compute `remainingPrerenderableParams` for each fallback shell: the unresolved pathname params that can still be filled by `generateStaticParams` in that branch. We then use that signal to gate partial fallback behavior.
With this change:
- `partialFallback` is only emitted for shells that still have prerenderable params remaining
- `allowQuery` with params is only preserved for those shells
Effectively, this limits partial fallback upgrading to shells that can still be made more specific by prerendering.
After a successful, Turbopack production build, this writes
`.next/diagnostics/route-bundle-stats.json`
containing First Load JS stats for every route.
## Why?
We used to have route bundle size statistics print after every build. It
was not very high-signal and could be misleading, often using heuristics
to give a best-effort estimate of what sizes may be at runtime. **These
kinds of metrics for route bundle sizes can be misleading, as pages can
be slow to visual/interactive completeness even if they have small first
load bytes.**
However, a minimal gauge of how much JavaScript a route ships during
initial page load can be a useful signal so long as users have
additional context and awareness of their app structure. Let's provide
just this number for now for those that could benefit.
Additionally, this does so in the form of writing a json document to the
build directory, rather than writing a table to stdout, where users have
needed to write extra tools to parse.
## Format
An array of route entries sorted descending by
`firstLoadUncompressedJsBytes`:
```json
[
{
"route": "/blog/[slug]",
"firstLoadUncompressedJsBytes": 124928,
"firstLoadChunkPaths": [
".next/static/chunks/framework.js",
".next/static/chunks/main.js",
".next/static/chunks/pages/blog/[slug].js"
]
}
]
```
`firstLoadUncompressedJsBytes`: sum of uncompressed sizes of all JS
chunks loaded on first navigation to the route (shared chunks included),
for both Pages Router and App Router
`firstLoadChunkPaths`: paths to those chunks relative to the project
root
## Test Plan
Added an integration test. Built with a create-next-app and verified the
json document is written and the numbers align with what Chrome reports
during initial load of a page.
### What?
Refactors the `ModuleReference` trait to make `chunking_type()` and `binding_usage()` methods return direct values instead of `Vc<T>` wrapped values, removing the need for async task functions.
Also removes the `get_referenced_asset` task from `EsmAssetReference`, inlining its logic into the callers.
### Why?
This change simplifies the API by eliminating unnecessary async overhead for methods that typically return simple, computed values. The previous implementation required `#[turbo_tasks::function]` annotations and `Vc<T>` wrappers even when the methods didn't need to perform async operations or benefit from caching.
### Impact
| Metric | Base | Change | Delta |
|--------|------|--------|-------|
| Hits | 35,678,143 | 35,845,124 | **+166,981** |
| Misses | 9,418,378 | 7,910,986 | **-1,507,392** |
| Total | 45,096,521 | 43,756,110 | **-1,340,411** |
| Task types | 1,306 | 1,277 | **-29** |
29 task types were removed, eliminating **2.6M total task invocations** (1.1M hits + 1.5M misses):
- **`chunking_type`** — 21 task types removed across all `ModuleReference` implementors (~952k invocations)
- **`binding_usage`** — 6 task types removed (~527k invocations)
- **`BindingUsage::all`** — helper task removed (~36k invocations)
- **`EsmAssetReference::get_referenced_asset`** — removed and inlined (~1.08M invocations: 628k hits + 451k misses)
The removed `get_referenced_asset` hits reappear as +628k hits on `EsmAssetReference::resolve_reference` and `ReferencedAsset::from_resolve_result` (with zero increase in misses), confirming the work is now served from cache through the existing callers.
No tasks had increased misses — the removal is clean with no cache invalidation spillover.
I also ran some builds to measure latency
```
# This branch
$ hyperfine -p 'rm -rf .next' -w 2 -r 10 'pnpm next build --turbopack --experimental-build-mode=compile'
Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
Time (mean ± σ): 52.752 s ± 0.658 s [User: 376.575 s, System: 106.375 s]
Range (min … max): 51.913 s … 54.161 s 10 runs
# on canary
$ hyperfine -p 'rm -rf .next' -w 2 -r 10 'pnpm next build --turbopack --experimental-build-mode=compile'
Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
Time (mean ± σ): 54.675 s ± 1.394 s [User: 389.273 s, System: 114.642 s]
Range (min … max): 53.434 s … 58.189 s 10 runs
```
so a solid win of almost 2 seconds
MaxRSS also went from 16,474,324,992 bytes to 16,359,309,312 bytes (from one measurement) so a savings of ~100M of max heap size.
### How?
- Changed `chunking_type()` method signature from `Vc<ChunkingTypeOption>` to `Option<ChunkingType>`
- Changed `binding_usage()` method signature from `Vc<BindingUsage>` to `BindingUsage`
- Removed `ChunkingTypeOption` type alias as it's no longer needed
- Updated all implementations across the codebase to return direct values instead of wrapped ones
- Removed `#[turbo_tasks::function]` annotations from these methods
- Updated call sites to use `into_trait_ref().await?` pattern when accessing these methods from `Vc<dyn ModuleReference>`
- Removed `EsmAssetReference::get_referenced_asset`, inlining its logic into callers
- Added validation for `turbopack-chunking-type` annotation values in import analysis
- Fixed cache effectiveness analysis script
### What?
- Add canonical edge entrypoint metadata to edge function definitions in
the middleware manifest.
- Surface that canonical entrypoint through `build-complete` as
`edgeRuntime` metadata in adapter outputs.
- Update middleware and adapter tests to assert the new metadata instead
of relying on bundler-specific emitted file names.
### Why?
Adapter consumers need a stable way to load and invoke edge outputs
without depending on `next-server`'s sandbox handling or guessing which
emitted file is the executable entrypoint. The existing checks were also
brittle across webpack and Turbopack because the emitted file lists can
differ.
### How?
- Add an optional `entrypoint` field to `EdgeFunctionDefinition` and
populate it in both the webpack middleware manifest generator and the
Turbopack manifest emitters for app, pages, and middleware edge
functions.
- In `packages/next/src/build/adapter/build-complete.ts`, use that
canonical entrypoint for `filePath` and expose `edgeRuntime.modulePath`,
`edgeRuntime.entryKey`, and `edgeRuntime.handlerExport` for edge
outputs.
- Adjust the middleware manifest tests to validate cross-bundler
invariants and add adapter-config coverage for the new `edgeRuntime`
fields.