This header is used by proxies to perform a concatenating resume render for partial prerendering. It is never sent from the client so we can filter it out when running in a mode that does not have an intermediate proxy.
we have auditing that server files aren't included in client chunks but the new htmlescape usage in next/script is causing that validation to fail. This refactors the utility into lib/shared
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.
Backport #93453 to 16.2
How was this ever working for dev? In that case, `buildId =
"development"` and so it always uses the same cache key
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
`pnpm dlx` isn't using a lockfile. Using the self-contained binary from
GitHub and validate the checksum lets us effectively pin `datadog-ci`
and all of its possible transitive dependencies.
This is a follow-up for
https://vercel.slack.com/archives/C0APPN2LC83/p1775073127894859
(cherry picked from commit 631fae951d)
Moves all release workflows off of a GH PAT and uses an app with a
short-lived token instead.
Test Plan:
Dry run
[here](https://github.com/vercel/next.js/actions/runs/24934593874).
However, this workflow is blocked until we figure out commit signing for
the bot app. Some options:
- The bot account generates a signing key and we use it in CI (not
great, bypasses the app)
- The org bypasses signature verification for the bot user (also not
great, requires an exemption rule)
- We need to rework the commit step so Lerna does not do the push, and
instead trigger it via the app + GH API. This seems like the best
option, will be added in a follow-up PR.
Note: `create-release-branch` workflow is broken in its current form, as
we will not be restoring administrator privileges to adjust environment
settings. This will become a manual step in the future.
(cherry picked from commit e69de2ffaf)
Moves the `environment` into `publishRelease` rather than
`trigger_release` since the actual action we want to create environment
rules for is the publish step.
(cherry picked from commit 2b846de4c5)
Switches from a long-lived token to trusted publishing OIDC flow. This requires a bump to Node (for feature support). Otherwise just dropping unnecessary envs.
(cherry picked from commit 129896c2e4)
## 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
Switch CI from self-hosted runners to GitHub-hosted runners and fix the
follow-up issues that showed up once the hosted jobs were exercised.
- Move Linux workflows onto `ubuntu-latest-16-core-oss`, Windows
workflows onto `windows-latest-8-core-oss`, and the native mac release
lane onto `macos-15-intel`.
- Update the reusable build workflow to work in hosted environments by
relying on `runner.os` instead of label string matching, and by removing
the hardcoded `/home/runner` `fnm` path.
- Make Turbo/sccache configuration explicit for hosted runners:
- unify on `vtest314-next-adapter-e2e-tests`
- pass `TURBO_TOKEN` via workflow env/secrets instead of assuming
runner-level env
- switch Turbo cache mode to `local:rw,remote:rw` so jobs still have
local cache behavior when remote cache is unavailable
- Update the `sccache` action defaults/docs to use the hosted-runner
secret setup (`TURBO_TOKEN`) instead of the old self-hosted token
wiring.
- Make Datadog reporting fail open so missing `DATA_DOG_API_KEY` does
not block CI.
- Fix the CLI reserved-port test so it asserts Next’s own `-p 1`
validation instead of failing earlier on hosted Linux due to
privileged-port binding.
- Replace the live `musl.cc` dependency in `native-builder.Dockerfile`
with musl sysroots imported from GHCR-hosted `rust-musl-cross` images,
keeping the existing `/opt/*-cross` layout used by the Linux native
build scripts.
- Fix the macOS native build workaround s so it applies to all
*-apple-darwin targets based on the Cargo target OS rather than the host
architecture, which avoids napi-build injecting the unsupported -Wl
linker arg when @next/swc is linked with rust-lld on hosted mac runners.
Test Plan:
- This PR's CI
- This
[build_and_deploy](https://github.com/vercel/next.js/actions/runs/24865715983/job/72801225722)
job running on every arch
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>
## 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 -->
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.
In a previous PR https://github.com/vercel/next.js/pull/88183, we added
`images.maximumResponseBody` but in only applied to external images.
This PR ensures the same config also applies to internal images.
### What?
In `ModuleBatchesGraph::get_entry_index`, when an entry module is not
found in the graph, the error message included the full list of all
possible entries (with their resolved string identifiers). This is an
expensive operation that requires async resolution of every entry's
`ident().to_string()`.
### Why?
In production builds, this verbose error output is unnecessary overhead
and produces very long error messages that are hard to read. The list of
possible entries is only useful during development/debugging.
### How?
Gate the expensive "possible entries" diagnostic behind
`cfg!(debug_assertions)`. In release builds, a short `bail!("Entry is
not in graph")` is used instead. In debug builds, the full diagnostic
with all possible entry identifiers is still shown to aid debugging.
This mirrors the same approach already applied to
`ChunkGroupInfo::get_index_of`.
<!-- NEXT_JS_LLM_PR -->
### What?
Fix an infinite recomputation loop in turbo-tasks by allowing cell
cleanup to proceed during recomputation even when the task result is an
error.
### Why?
When a task errors during normal execution, cell cleanup is
intentionally skipped. The rationale is that errors during normal
execution are often transient eventual-consistency issues caused by
reading stale inputs — keeping old cell data prevents "cell no longer
exists" errors for dependent tasks that are reading those cells.
However, during **recomputation** (a task re-executing without being
marked dirty), nothing about its inputs has changed. Any error it
produces is therefore not transient. If cell cleanup is skipped in this
case, stale cells remain in place, which trigger further recomputations,
which produce further errors, which skip further cleanups — creating an
infinite loop.
### How?
- Detect whether a task execution is a recomputation by checking
`task.is_dirty().is_none()` at completion time (a recomputing task was
not dirty when it started executing).
- Propagate `is_recomputation` through
`TaskExecutionCompletePrepareResult` to both
`task_execution_completed_prepare` and
`task_execution_completed_cleanup`.
- In both places where cell cleanup is gated on `result.is_ok()` /
`!is_error`, extend the condition to also allow cleanup when
`is_recomputation` is true.
This ensures that stale cells produced by a erroring recomputation are
cleaned up, breaking the infinite loop.
<!-- NEXT_JS_LLM_PR -->
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
### 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 -->
## Summary
- The notify `Config` uses a builder pattern where `with_follow_symlinks()` consumes `self` and returns a new `Config`, but the return value was being discarded — so `follow_symlinks` remained at its default (`true`).
- The `RecommendedWatcher` branch was also passing `Config::default()` instead of the configured `config` object, ignoring the config entirely.
## Test plan
- [x] `cargo check -p turbo-tasks-fs` passes
- Behavioral: symlinks under the watched root will now trigger events for the symlink itself rather than the target, matching the intent of the existing comment.
<!-- NEXT_JS_LLM_PR -->
Fixes#91653
Running `next dev` with the latest Turbopack fails to fetch fonts on
Windows ARM machines, resulting in an `http2 feature is not enabled`
error. This issue was fixed in `reqwest` v0.13.2:
https://github.com/seanmonstar/reqwest/pull/2927
### What?
Fix server HMR becoming unresponsive after a metadata route is loaded in
the same Node.js process as an app page.
### Why?
Turbopack loads separate runtime chunks for app pages and metadata
routes (`robots.ts`, `sitemap.ts`, `manifest.ts`, `icon.tsx`, etc.) in
the same Node.js process. Each runtime chunk embeds `dev-nodejs.ts` and
produces a distinct `__turbopack_server_hmr_apply__` closure bound to
its own `moduleFactories` and `devModuleCache`.
Previously each runtime simply overwrote
`globalThis.__turbopack_server_hmr_apply__`, so the last chunk to load
silently won. Navigating to `/robots.txt` before an HMR update caused
the metadata route runtime to overwrite the page runtime's handler.
Subsequent HMR updates were dispatched only to the metadata route
runtime, which has no knowledge of the page module — the page appeared
frozen and stopped reflecting file changes.
### How?
Replace the bare assignment with a multicast registry:
1. Each runtime appends its own `__turbopack_server_hmr_apply__` handler
to `globalThis.__turbopack_server_hmr_handlers__[]`.
2. The first runtime to register installs a shared dispatcher as
`globalThis.__turbopack_server_hmr_apply__` that iterates all registered
handlers at call time (not install time), so newly loaded runtimes are
always included.
3. On full cache reset, `hot-reloader-turbopack.ts` resets
`__turbopack_server_hmr_handlers__` to `[]` so stale handlers from
evicted chunks don't accumulate into the next generation.
Because `dev-nodejs.ts` is embedded into the Turbopack binary via
`include_dir!`, this fix requires rebuilding the native binary.
### Tests
`test/development/app-dir/server-hmr/server-hmr.test.ts` — extended with
`metadata route hmr` tests that load a metadata route before patching a
page file, verifying HMR updates still reach the page runtime after a
second runtime chunk is loaded.
---------
Co-authored-by: Will Binns-Smith <wbinnssmith@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Adds deduplication and conflict detection to the asset emission stage in
`crates/next-core/src/emit.rs`, and a new `IssueStage::Emit` variant in
`turbopack-core`.
Before emitting, assets are grouped by their output path. If multiple
assets map to the same path:
- If their content is identical, one is silently chosen (deduplication).
- If their content differs, both versions are written to
`<node_root>/<content_hash>.<ext>` and an `EmitConflictIssue` is raised
for each conflict. All assets are still emitted — conflicts do not abort
the build.
Previously, duplicate output assets for the same path were emitted
unconditionally — whichever write happened last silently won. This
masked build graph bugs where two different modules produced conflicting
output files. Reporting conflicts as issues (rather than silently
overwriting) makes them visible and easy to diagnose without breaking
the build.
- Collect all assets with their resolved paths via `try_flat_join`.
- Bucket them into two `FxIndexMap<FileSystemPath,
Vec<ResolvedVc<Box<dyn OutputAsset>>>>` — one for node-root assets and
one for client assets.
- For each bucket entry, call `check_duplicates`: compare every asset
against the first using `assets_diff`. If content differs, emit an
`EmitConflictIssue` as a turbo-tasks collectible — but still return the
first asset so emission continues.
- `assets_diff` is a `#[turbo_tasks::function]` that takes only
`(asset1, asset2, extension, node_root)` — the `asset_path` stays out of
the task key to avoid unnecessary task cardinality. When file content
differs, it hashes each version with xxh3, writes them to
`<node_root>/<hash>.<ext>`, and returns the paths in the detail message
so the user can diff them.
- `EmitConflictIssue` implements the `Issue` trait with
`IssueStage::Emit` (new variant added to `turbopack-core`),
`IssueSeverity::Error`, a descriptive title, and a detail message
explaining the type of conflict.
- Node-root and client assets are emitted in parallel via
`futures::join!` (not `try_join!`) to ensure deterministic error
reporting — both branches always run to completion so errors are
reported in a consistent order.
---------
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>