3485 Commits

Author SHA1 Message Date
Tim Neutkens afa053d9eb Turbopack: Match proxy matchers with webpack implementation (#93594)
## What?

Applies
https://github.com/vercel/next.js/commit/d166096c399c4fc4e09cd2d1bf26dca6579a855d
for canary and handles the Turbopack-side implementation for
`middleware-manifest.json` which is used for edge runtime middleware.ts

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
2026-05-07 19:32:15 +02:00
Tim Neutkens 97a154e5bb Turbopack: Fix middleware matcher suffix (#93590)
## What?

The Turbopack path incorrectly included a double backslash instead of
one backslash in the regex matcher.
2026-05-07 19:32:04 +02:00
Sebastian "Sebbie" Silbermann 7b222b9095 [backport][test] Pin package manager to patch versions (#93595) 2026-05-07 15:32:13 +02:00
Zack Tanner 0dd94836a8 fix: add explicit checks for RSC header (#83) (#98)
(cherry picked from commit 807e363b13cc9395aa74f75122d1d16b4a46dc1a)
2026-05-06 18:02:07 +02:00
JJ Kasper cb171d7494 Fix i18n middleware matching for default-locale data routes (#82)
* Fix i18n middleware matching for default-locale data routes

* unskip node middleware on deploy
2026-05-06 17:59:10 +02:00
Josh Story 66f6017f15 Escape properties for beforeInteractive scripts (#86)
props set on a next/script with beforeInteractive are serialized as JSON into an inline script to be exectuted prior to hydration by Next.js. The props were not additionally escaped and if user input was rendered as through a script it could allow fro XSS. This has alwways been true if you pass user input into the script body or via dangerouslySetInnHTML however and it is always the developers responsibility to sanitize user input before rendering. However wherever React has some automatic escaping to act as an additional defense against common mistakes leading to vulnerabilities like XSS Next.js should mirror that becasue from the user's perspective it is not clear that React is not directly rendering the script props and content.

This change adds additional escaping so that serialized properties cannot break out of the script scope and execute unexpected payloads.
2026-05-06 17:59:10 +02:00
Hendrik Liebau 3d98505a24 [backport] fix: preserve HTTP access fallbacks during prerender recovery (#93470)
Backports:

- #92231

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Co-authored-by: Andrew Clark <git@andrewclark.io>
2026-05-06 14:49:29 +02:00
Hendrik Liebau f1c11203d5 [backport] Fix double-encoding of URL pathname parts in client param parsing (#93506)
Backports:

- #93491
2026-05-06 11:51:44 +02:00
Hendrik Liebau 2d08397b3d [backport] fix accidental test duplication (#93507) 2026-05-05 23:54:20 +02:00
JJ Kasper f6bda26ef9 Fix fallback route params case in app-page handler (#93109)
## Summary
- backport the fallback route params fix from #91737 onto `next-16-2`
- preserve intermediate shells that suspend only for unresolved fallback
params during staged rendering
- add the missing `resolvedRouteParamKeys` request metadata needed by
this branch
2026-04-27 12:28:09 -07:00
Zack Tanner af0e96ba23 Fix invalid HTML response for route-level RSC requests in deployment adapter (#91541)
In a deployed environment, `onCacheEntryV2` determines whether a
postponed response is HTML-origin or RSC-origin by checking whether
meta.url ends with `.rsc`.

For some dynamic full-route RSC requests, Next was passing a URL with
query params into onCacheEntry, for example:
`/[tenant]/samples.rsc?nxtPtenant=tenant-x`

Because that string does not end with .rsc, `onCacheEntryV2`
misclassified the request as HTML and treated it as text/html instead of
an RSC response.

This PR normalizes the URL before passing it to `onCacheEntry`, so the
callback receives the pathname rather than the full URL-with-query.

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2026-04-24 06:18:16 -07:00
Zack Tanner 2cdb7ed34f [tests]: fix cache-components.test.ts type error (#93113)
Fixes a type error on canary after merging two PRs that touched the same
test.
2026-04-21 18:29:42 -07:00
JJ Kasper 8cd3fdc111 test: scope css data-url typing to fixture (#91877)
In the latest TypeScript version these imports now fail so updating the
assertion to handle them

x-ref:
https://github.com/vercel/next.js/actions/runs/23508766037/job/68431989897#step:35:443
2026-04-21 18:28:36 -07:00
JJ Kasper 6fd09bf8ab Patch setHeader for direct route handlers (#93101)
## Summary
- apply the `setHeader` cookie-support patch to direct route module
handlers in `prepare()`
- make `patchSetHeaderWithCookieSupport()` idempotent so both entry
paths can call it safely
- add an adapter-like direct RSC regression test that fails without the
patch

Closes: NEXT-4919

## Testing
- `NEXT_SKIP_ISOLATE=1 NEXT_TEST_MODE=start pnpm testheadless
test/e2e/app-dir/cache-components/cache-components.test.ts -t "should
ignore late setHeader calls for direct RSC handlers after headers are
sent"`

<!-- NEXT_JS_LLM_PR -->
2026-04-21 16:46:27 -07:00
Zack Tanner 688ed31e21 Strengthen _rsc cache-busting param (#92755)
This updates `_rsc` cache-busting param generation to use a stronger
digest so different RSC request variants are much less likely to produce
the same value.

It also updates the related request-validation and injected
resume/instant-test paths so they continue to generate and validate the
same `_rsc` values across client, server, and edge-reachable codepaths.
2026-04-21 16:46:26 -07:00
JJ Kasper 15341fdf49 Ensure x-nextjs-data header is only set during resolve (#92752)
Makes sure we set this header during route resolving so user request
doesn't need to.
2026-04-21 13:53:59 -07:00
Andrew Imm 6f3808e204 Compiler: Support boolean and number primtives in next.config defines (#92731)
Expand `config.compiler.define` to support number and boolean primitives
as well.

The support is already in Turbopack's rewrite engine, as well as
Webpack's DefinePlugin. We just need to modify the zod schema to
actually support passing numbers and bools.

Added an E2E test that should check all supported types.
2026-04-14 17:32:17 -07:00
Luke Sandberg fbc7684ecb Scope Safari ?ts= cache-buster to CSS/font assets only (Pages Router) (#92580)
### What?

Refactors the Safari `?ts=` cache-busting workaround in the Pages Router so it **only appears on CSS and font URLs**, not on script tags or script preload links.

### Why?

The `?ts=` timestamp was originally added to all preloaded assets as a workaround for a [Safari caching bug](https://bugs.webkit.org/show_bug.cgi?id=187726) (see [#5860](https://github.com/vercel/next.js/issues/5860)). When it appears on `<script>` tags, the Turbopack runtime's `getAssetSuffixFromScriptSrc()` reads the executing script's query string and infers it as the `ASSET_SUFFIX`, which then leaks onto all static asset URLs — including images. This causes `next/image` validation errors because the image URL gets an unexpected `?ts=` parameter.

Fixes #92118

### How?

Instead of maintaining parallel `assetQueryString` (with `?ts=`) and `scriptAssetQueryString` (without `?ts=`) paths, this PR:

1. **`assetQueryString`** carries only the deployment token (`?dpl=...`) and is used for all script-related URLs (script tags, script preloads)
2. **`safariCacheBuster`** is a new, separate field (`?ts=<timestamp>` or `""`) that is only combined with `assetQueryString` at the 3 CSS/font URL sites via a `joinQueryStrings()` helper
3. Removes the `scriptAssetQueryString` / `scriptMutableAssetQueryString` / `cssAssetQueryString` fields entirely

The Safari cache-buster is computed the same way as before (dev server only, Safari user-agent check) — it just no longer contaminates the base query string.

### Test plan

- [x] Updated existing test in `test/e2e/app-document/rendering.test.ts` to assert `?ts=` appears only on `<link rel="preload" as="style">` and `<link rel="preload" as="font">`, and does NOT appear on `<script>` or `<link rel="preload" as="script">`
- [x] `pnpm --filter=next types` passes

<!-- NEXT_JS_LLM_PR -->
2026-04-14 16:55:19 -07:00
Tobias Koppers f158df18bd Fix styled-jsx race condition: styles lost due to concurrent rendering (#92459)
### What?

Fix a race condition in the Pages Router SSR path where styled-jsx
styles were dropped from the rendered HTML.

### Why?

`styledJsxInsertedHTML()` reads and flushes the styled-jsx style
registry. Previously it was called concurrently with the page render via
`Promise.all`:

```js
const [rawStyledJsxInsertedHTML, content] = await Promise.all([
  renderToString(styledJsxInsertedHTML()),
  (async () => { /* render the page */ })(),
])
```

Because both ran at the same time, `styledJsxInsertedHTML()` could (and
in practice did) execute and flush the registry **before** the page
render had finished populating it. The result was that dynamic
styled-jsx styles — those with interpolated expressions that compute
their class names at runtime via DJB2 hashing — were silently dropped
from the SSR output, causing a flash of unstyled content on first load.

This is particularly visible in production deployments where all
components use dynamic styled-jsx (numeric `jsx-*` class names), since
those styles only exist in the registry after rendering completes.

### How?

Serialize the two operations: render the page first, then call
`styledJsxInsertedHTML()`. Since the registry is fully populated by the
time it is read, all styles are captured correctly.

A new e2e test (`test/e2e/styled-jsx-dynamic`) exercises this scenario
with multiple nested components that all use dynamic styled-jsx with
interpolated props, covering the exact FOUC pattern seen in production.

<!-- NEXT_JS_LLM_PR -->

Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-07 12:07:04 -07:00
JJ Kasper edcf19ae13 Backport: TypeScript v6 deprecations for baseUrl and moduleResolution (#92130)
backports: https://github.com/vercel/next.js/pull/91855
backports: https://github.com/vercel/next.js/pull/91847
2026-03-31 09:38:57 -07:00
JJ Kasper 600cd2fedb Fix adapter outputs for dynamic metadata routes (#91680)
## Summary

This fixes adapter `onBuildComplete` outputs for dynamic metadata
routes.

## Bug

`handleBuildComplete` skipped metadata routes too aggressively via
`isStaticMetadataFile(...)`, so dynamic metadata routes (for example
`robots.txt` / `sitemap.xml` using `connection()`) were omitted from
`outputs.appRoutes`.

## Fix

Only skip metadata routes when they are actually prerendered/static
(present in prerender manifests). Dynamic metadata routes are now
included in adapter `outputs.appRoutes` as expected.

## Tests

Added e2e coverage in `test/e2e/app-dir/adapter-dynamic-metadata`:
- verifies dynamic `robots.txt`, `sitemap.xml`, and `favicon.ico`
functionality
- verifies adapter output classification for non-deploy runs
- skips output-shape verification in deploy mode
- skips the suite in dev mode
2026-03-20 14:30:40 -07:00
Niklas Mischkulnig 6cb97d6d75 Turbopack: lazy require metadata and handle TLA (#91705)
A regression from #88487

1. Make metadata import lazy with `() => require()`, just like for the layout segments
2. Properly await the return value to better handle TLA modules

This align with Webpack which does this:

<img width="1535" height="509" alt="Bildschirmfoto 2026-03-20 um 11 58 00" src="https://github.com/user-attachments/assets/f2864c86-ccce-4884-8417-c8ae06c05f78" />


Closes PACK-6927
Closes #91700
Closes https://github.com/vercel/next.js/issues/91676
2026-03-20 14:28:49 -07:00
Luke Sandberg e6b101ae71 [turbopack] Respect {eval:true} in worker_threads constructors (#91666)
### What?

Fixed Turbopack incorrectly trying to resolve inline JavaScript code as module references when `new Worker()` is called with `{ eval: true }` option.

Now we skip creating a reference when `eval:true`, and report warnings if we cannot tell what the value is

### Why?

Libraries like jsPDF create Worker threads by passing inline JavaScript code as the first argument along with `{ eval: true }` as the second argument. Turbopack was incorrectly treating this inline code as a file path and attempting to resolve it as a module reference, causing build failures.

### How?

Added logic to detect when the `eval: true` option is passed to the Worker constructor. When this option is present, Turbopack now skips creating a worker reference since the first argument contains executable code rather than a file path. Added comprehensive test coverage using jsPDF to verify the fix works correctly.

Fixes #91642
2026-03-20 14:28:49 -07:00
Hendrik Liebau 649d302635 Unflake router events deploy test (#91589)
[Flakiness
metrics](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.status%3A%22fail%22%20%40test.name%3A%22basePath%20should%20use%20urls%20with%20basepath%20in%20router%20events%20for%20hash%20changes%22&agg_m=count&agg_m_source=base&agg_t=count&currentTab=overview&eventStack=&fromUser=false&index=citest&start=1773244604685&end=1773849404685&paused=false)

After upgrading Playwright in #91250 (which brought some performance
improvements), some tests are now exposed to be missing retries around
assertions. `test/e2e/basepath/router-events.test.ts` is one of those
tests, and this PR adds a `retry` around the event log assertion. We're
also replacing the deprecated `check` utility with `retry` when waiting
for the router to be ready.
2026-03-18 16:34:53 +00:00
Josh Story 0ca967b863 Add group depth tracking to instant validation boundary discovery (#91208)
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
2026-03-17 22:14:19 -07:00
Tim Neutkens 97f420911d Move adapterPath from experimental to stable top-level config (#91535)
## 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
2026-03-17 23:28:08 +01:00
Hendrik Liebau 89c6e73c81 Unflake two "use cache" tests (#91529)
[Flakiness
metric](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.name%3A%28%22use-cache%20should%20cache%20results%20for%20cached%20functions%20imported%20from%20client%20components%22%20OR%20%22use-cache%20should%20cache%20results%20for%20cached%20functions%20passed%20to%20client%20components%22%29%20%40test.status%3A%22fail%22%20%40duration%3A%3E%3D1ns&agg_m=count&agg_m_source=base&agg_t=count&citest_explorer_sort=timestamp%2Casc&cols=%40test.status%2Ctimestamp%2C%40test.suite%2C%40test.name%2C%40duration%2C%40test.service%2C%40git.branch&currentTab=overview&eventStack=&fromUser=false&index=citest&start=1772476060657&end=1773772060657&paused=false)

I'm not sure why these tests recently got much more flaky (they were
somewhat flaky before), but they definitely were missing `retry` blocks
around the assertions after clicking the reset button.
2026-03-17 12:47:27 -07:00
Tobias Koppers 4494261bd7 Remove devCacheControlNoCache experimental option (hard-code no-cache) (#91503)
### 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>
2026-03-17 20:13:01 +01:00
Jiwon Choi 6db019c330 Skip catch-error-react-compiler test to unblock CI (#91528)
https://github.com/vercel/next.js/actions/runs/23170738194/job/67327462789#step:36:318
is holding up CI, including release. Cause is not from `catchError` PR,
it's just that the test has discovered missing support of Next.js for
Pages + React 17/18 + React compiler. Will work in parallel.
2026-03-17 19:55:55 +01:00
Andrew Clark 568c7029ab Track vary params during runtime prefetches (#89297)
**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.
2026-03-17 13:58:23 -04:00
Hendrik Liebau a46ff7c861 Error when accessing undefined root params in generateStaticParams (#91519)
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`.
2026-03-17 17:27:50 +00:00
Jiwon Choi 66478522a8 Improve RSC error messages (#90702)
Improve the RSC error messages to be more comprehensive.

x-ref:
https://github.com/vercel/next.js/pull/89688#discussion_r2865847895

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-17 14:37:26 +01:00
Andrew Clark a060fbf9f7 Deflake per-page dynamic stale time test (#91492)
## Summary

- Fixes flaky test "per-page value overrides global staleTimes.dynamic
regardless of direction" introduced by #91437
- The test was flaky because `browser.back()` restored accordion state
from BFCache, causing previously-opened `LinkAccordion` links to be
immediately visible. This triggered uncontrolled re-prefetches outside
the `act` scope. When the `IntersectionObserver` fired inside a
subsequent `act` scope (after clock advancement), stale data would
trigger a prefetch that violated the `no-requests` assertion.
- Fix: instead of navigating back to a previously visited page, navigate
forward to fresh "hub" pages with their own `LinkAccordion` components.
Since these are never-visited pages, accordions start closed and no
uncontrolled prefetches are triggered.
- General principle: when using the router `act` test utility, always
use `LinkAccordion` to control when prefetches happen. Prefetches should
only be triggered inside an `act` scope by toggling an accordion, never
by navigating back to a page where links are already visible.

## Test plan

- [x] Ran the full test file 4 times locally — all 5 tests pass
consistently
2026-03-17 09:14:19 -04:00
Hendrik Liebau 8283b1260b Support accessing root params in "use cache" functions (#91191)
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).
2026-03-17 14:04:44 +01:00
Zack Tanner a27a11d78e Disallow Server Action submissions from privacy-sensitive contexts (#91478)
See:
https://github.com/vercel/next.js/security/advisories/GHSA-mq59-m269-xvcx
and [16.1.7](https://github.com/vercel/next.js/releases/tag/v16.1.7)

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <sebastian.silbermann@vercel.com>
2026-03-16 18:57:51 -07:00
Jiwon Choi fa32daad8d Add unstable_catchError() API for custom error boundary (#89688)
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>
2026-03-16 22:57:11 +00:00
Sebastian "Sebbie" Silbermann 672b02b270 [next-playwright] Use unique cookie values for instant navigation testing lock (#91250)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-03-16 21:07:56 +00:00
Andrew Clark 5dd48e3598 Add unstable_dynamicStaleTime route segment config (#91437)
## 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>
2026-03-16 15:38:17 -04:00
Hendrik Liebau 9bec0cce14 Avoid undefined outer work unit store in "use cache" (#91190)
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.
2026-03-16 16:40:16 +01:00
Hendrik Liebau 4f69336336 Support accessing root params in generateStaticParams (#91189)
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.
2026-03-16 15:44:53 +01:00
Tobias Koppers 261922df82 Show generated code from loaders in parse error messages (#89898)
## 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>
2026-03-16 13:41:29 +01:00
Luke Sandberg 2e4f08ca0a fix(build): don't block SSG on telemetry flush, add persistence spans to trace-build (#91335)
### 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`
2026-03-15 23:42:17 -07:00
Andrew Clark 83e47acd77 Simplify scroll restoration with shared ScrollRef on CacheNode (#91348)
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.
2026-03-14 13:38:36 +01:00
Zack Tanner cd49df2d22 disable sub shell generation test outside of adapter (#91353)
These tests have started failing in deploy mode in environments outside
of the new Vercel adapter. Shell related behavior is changing as part of
the new `partialFallbacks` flag and will not be backported to the old
CLI, so this gates the test to ensure it runs in the right deployment
environment.
2026-03-14 03:22:06 +00:00
Zack Tanner ae3f9f5187 partial fallbacks: complete generic shells into more specific shells (#91231)
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.
2026-03-13 18:21:20 -07:00
Zack Tanner cf4d3ae89c partial fallbacks: only upgrade shells with remaining prerenderable params (#91158)
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.
2026-03-13 17:35:14 -07:00
Tobias Koppers e22988e5ee Turbopack: switch chunk/asset hashes from hex to base40 encoding (#91137)
### What?

Switch Turbopack's hash encoding for chunk and asset output filenames
from hexadecimal (base16) to base40, using the alphabet \`0-9 a-z _ - ~
.\`. Version hashes (used for HMR update comparison, not filenames) use
base64 instead.

### Why?

Base40 encodes the same number of bits in fewer characters than hex,
producing shorter output filenames. All 40 characters are RFC 3986
unreserved (URL-safe) and safe on case-insensitive filesystems (macOS
HFS+/APFS, Windows NTFS).

Hash truncation lengths are reduced proportionally to maintain
equivalent collision resistance:

| Context | Before (hex) | After (base40) | Entropy |
|---|---|---|---|
| Content hash in chunk filenames | 16 chars | 13 chars | ~69 bits |
| Content hash in asset filenames | 8 chars | 13 chars | ~69 bits |
| Ident disambiguator hash | 8 chars | 7 chars | ~37 bits |
| Long-path prefix hash | 5 chars | 4 chars | ~21 bits |

### How?

**New encoding module** (\`turbo-tasks-hash/src/base40.rs\`):
- Defines the base40 alphabet and length constants (\`BASE40_LEN_64 =
13\`, \`BASE40_LEN_128 = 25\`)
- Implements a generic \`encode_base40_fixed<N>\` helper to avoid
duplication
- Public API: \`encode_base40(u64) -> String\` and
\`encode_base40_128(u128) -> String\`

**New base64 encoding** (\`turbo-tasks-hash/src/base64.rs\`):
- \`encode_base64(u64) -> String\` — 11-char base64 (no padding) for
version hashes
- Version hashes don't appear in URLs or filenames, so base64 is safe
and shorter

**New \`HashAlgorithm\` variants** (\`turbo-tasks-hash/src/lib.rs\`):
- \`Xxh3Hash64Base40\` and \`Xxh3Hash128Base40\` added alongside
existing hex variants
- Existing hex variants kept for internal manifests and identifiers

**\`ContentHashing\` moved to \`turbopack-core\`**:
- Moved from \`turbopack-browser\` to
\`turbopack-core/src/chunk/mod.rs\` so both \`BrowserChunkingContext\`
and \`NodeJsChunkingContext\` can use it

**Separate chunk vs asset content hashing**:
- \`BrowserChunkingContext\`: \`content_hashing\` renamed to
\`chunk_content_hashing\` (optional), new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- \`NodeJsChunkingContext\`: new \`asset_content_hashing:
ContentHashing\` field (non-optional, defaults to 13 chars)
- Builder methods: \`use_content_hashing()\` renamed to
\`chunk_content_hashing()\`, new \`asset_content_hashing()\`

**Version hashes switched to base64**:
- \`turbopack-nodejs/src/ecmascript/node/version.rs\`
- \`turbopack-dev-server/src/html.rs\`
- \`turbopack-browser/src/ecmascript/version.rs\`,
\`merged/version.rs\`, \`list/version.rs\`

**Other callers updated** (15 files across turbopack and next-core):
- All chunk/asset content hashing switched from \`Xxh3Hash128Hex\` →
\`Xxh3Hash128Base40\`
- \`ContentHashing::Direct { length }\` reduced from 16 → 13
- Asset path truncations use full 13-char base40 hash (matching chunk
filenames)

**Exception — \`wasm_edge_var_name\`** (\`turbopack-wasm/src/lib.rs\`):
- Kept as \`Xxh3Hash128Hex\` because the hash is used as part of a
JavaScript variable name (\`wasm_{hash}\`), and base40 characters \`-\`,
\`~\`, \`.\` are not valid JS identifier characters.

**Scope — NOT changed:**
- Webpack configuration (unchanged)
- Internal manifests (\`routes_hashes_manifest\`,
\`project_asset_hashes_manifest\`)
- Internal identifiers (font naming, external module hashing, data URI
sources, debug IDs)
- SRI hashes (SHA-based Base64, different purpose)

---------

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 20:38:02 +00:00
JJ Kasper 07189c5224 Expose edge runtime fields in build-complete (#91239)
### 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.
2026-03-13 18:22:47 +00:00
JJ Kasper a6b36200ff Wire cache handlers in edge paths and add e2e regression coverage (#91236)
### What?

This PR wires custom `cacheHandler` / `cacheHandlers` through the
remaining edge entrypoints so edge app pages, edge app routes, edge
pages SSR, middleware, and edge API routes can all see the configured
handlers.

It also adds an e2e regression suite covering:
- pages-router ISR revalidation with a custom incremental cache handler
- app-router cache handlers with cache components enabled
- edge app page + edge app route wiring when cache components are
disabled

### Why?

We already support custom cache handlers in non-edge paths, but several
edge code paths were not forwarding that configuration into the
generated entrypoints/runtime wrappers. That meant custom cache handlers
could be skipped in edge rendering and edge route execution, and we did
not have regression coverage around those cases.

### How?

- pass `cacheHandler` / `cacheHandlers` through the JS build entry
plumbing for edge server entries
- inject cache handler imports/registration into the webpack edge
templates and loaders
- mirror the same wiring in the Turbopack/Rust entry generation for edge
app pages, app routes, pages SSR, and middleware
- update the edge route wrapper to initialize and register cache
handlers before invoking the route module
- extend `next-taskless` template expansion with raw injection support
so the generated edge templates can add imports and registration code
- add `test/e2e/cache-handlers-upstream-wiring` fixtures to cover
pages/app, edge/non-edge, and revalidation behavior
2026-03-13 17:25:59 +00:00
Andrew Clark 0e6ab3f548 [Prefetch Inlining] Generate size-based hints on server (#90891)
Part 1 of 2. This commit adds the server-side infrastructure for
size-based segment bundling but does not change any observable behavior.
The client-side changes that actually consume bundled responses are in
the next commit.

At build time, a measurement pass renders each segment's prefetch
response, measures its gzip size, and decides which segments should be
bundled together vs fetched separately. The decisions are persisted to a
manifest and embedded into the route tree prefetch response so the
client can act on them.

The decisions are computed once at build and remain fixed for the
lifetime of the deployment. They are not recomputed during
ISR/revalidation — if they could change, the client would need to
re-fetch the route tree after every revalidation, defeating the purpose
of caching it independently.

Refer to the next commit for a full description of the design and
motivation.

## Config

experimental.prefetchInlining accepts either a boolean or an object with
threshold overrides (maxSize, maxBundleSize). When true, the default
thresholds are used (2KB per-segment, 10KB total budget). The auto
behavior will eventually become the default. The config will remain
available for overriding thresholds.
2026-03-13 12:09:25 -04:00