mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/fallback-root-cache
567 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7b58e5880c |
Turbopack: Add support for specifying additional roots (#98003)
Full motivation and plan here: https://app.notion.com/p/vercel/Turbopack-pnpm-Global-Virtual-Store-383e06b059c480579403ddfd71cc2d40?source=copy_link The goal is to allow `DiskFileSystem` to traverse outside of it's own root to other configured `DiskFileSystem`s when following symlinks. We may allow traversal in other situations in the future, but this is limited to symlink resolution for now. ## Global Virtual Store The motivation for this is to enable [pnpm's Global Virtual Store feature](https://pnpm.io/global-virtual-store) (and there are other package managers doing this, including nub and bun). We'd expose the ability to manually configure this in `next.config.js`, but we should also auto-configure ourselves for popular package managers (or at least make a best effort to do so, the `PNPM_HOME` semantics can be complicated). The `ignoreIfMissing` option is provided for this situation: We can configure a bunch of roots automatically, and they only actually get set up if they exist, the check for directory existence is cheap. ## NFT changes This requires a couple extensions to the `*.nft.json` file format: https://github.com/vercel/next.js/pull/98469 ## Related Issues - #93556 - https://github.com/pnpm/pnpm/issues/14972 |
||
|
|
469a7c5573 |
Turbopack: Add symlinks and additional roots to NFT metadata (#98469)
Implements the following extension:
```typescript
export interface NftFileList {
// File paths relative to the directory containing the `.nft.json` file, or
// the current root (if inside of `NftAdditionalRoot`).
//
// When using webpack, these paths may exist outside of the tracing root. The
// [`@vercel/next` package ignores these paths][vc-next].
//
// When using Turbopack, these paths are all guaranteed to exist within the
// `turbopack.root` specified or inferred from the `next.config.js` file.
//
// [vc-next]: https://github.com/vercel/vercel/blob/%40vercel/next%404.20.5/packages/next/src/server-build.ts#L1022-L1026
files: string[]
// Turbopack extension: A parallel array to `files` (same indices and length)
// with file content hashes. For symlinks, the hash of the path of the target
// is stored.
fileHashes?: string[]
// Turbopack extension: Explicit symlink mapping information.
//
// If included, it's safe to assume that if a file is not in `symlinks` that
// it is not a symlink. If this is an empty array, there are no symlinks.
//
// If omitted, the processor of the nft file must call `read_link` on every
// file to determine if it is a symlink and determine the target path.
//
// This field is always included if `NftJson` includes `additionalRoots`, and
// it is always included on `NftAdditionalRoot`.
symlinks?: NftSymlink[]
}
export interface NftJson extends NftFileList {
version: 1
// Turbopack extension: A hash of the entrypoint that refers to these traced
// files. This hash only depends on the content of the entrypoint file, and
// not all of its traced dependencies.
entryHash?: string
// Turbopack extension: Paths stored with different base paths, typically
// outside of the tracing root.
additionalRoots?: NftAdditionalRoot[]
}
// Turbopack extension: A collection of paths stored with a different base path.
export interface NftAdditionalRoot extends NftFileList {
// Stable unique identifier provided in the `next.config.js`. This can be used
// to generate the output path where these files are copied to (e.g.
// `.additionalRoots/$[name}`).
//
// This is guaranteed to use a character set that is valid on most
// filesystems, and the identifiers are guaranteed to not have overlaps on
// case-insensitive filesystems.
name: string
// A source path on the build machine that the paths in `files` are relative
// to. The final build output directory should not depend on this path.
absolutePath: string
// Always specified on NftAdditionalRoot.
symlinks: NftSymlink[]
}
// Turbopack extension: Information on a symlink, including which additional
// root it maps to. Symlinks that do not cross root boundaries (the common case)
// omit the index into `additionalRoots`.
//
// It is often complicated to transform raw symlink targets to root-relative
// paths, and including this information here ensures that the NFT reader gets
// the same result that Turbopack's tracing system expects.
//
// Because the link target type is unspecified, on Windows the reader needs to
// call `stat` to determine if a link target is a directory or file.
export type NftSymlink =
| [
// Index of `files` that refers to a symlink.
number,
// The target path of the link. In `NftJson`, this path is relative to the
// directory containing the `.nft.json` file. In `NftAdditionalRoot`, this
// is relative to the current root.
string,
]
| [
// Index of `files` that refers to a symlink.
number,
// The target path of the link relative to the specified root.
string,
// An index into `additionalRoots`, -1 if the target path is relative to
// the `.nft.json` file's directory,
//
// If the symlink target is relative to the same root as the symlink
// itself (the "current root"), this field is omitted.
number,
]
```
Nothing currently populates `additionalRoots`.
https://github.com/vercel/next.js/pull/98003 will do it.
|
||
|
|
e6688470ef |
Properly disable laziness on next/dynamic (#98828)
next/dynamic assumes a eager semantic for css gathering, we are making sure that holds even when lazy dynamic imports are enabled |
||
|
|
5d9ab72cef |
Add next upgrade --ai and security vulnerability coverage (#98562)
> [!TIP] > Recommended to review commit by commit. This PR adds `next upgrade --experimental-ai="security"` flag (alias `--ai`), which is targeted to help users leverage agents to upgrade their app to the safe major version when their app's Next.js version has any security advisories. Once the command is ran from the user, Next.js will detect the installed agent harness in user's device, currently limited to Codex and Claude, and will proceed with starting an agent session once approved. If it is called within an agent session, the work will continue off within that agent. `next upgrade --ai` simply does two things: - prepare the relevant context to temporary dir - print hand off prompt, guiding to read those context The context will guide the agent to run relevant codemods and migration checklist to proceed. This PR is a base core of the workflow, and will have wrappers of entry point around this. Also, will add "latest" and "future" as follow up, which will cover the app to be always latest, and adopt the future defaults like Cache Components. This PR also sets up the evals infra and adds evals. |
||
|
|
16c8ea5d55 |
Prefer using join over try_join when you don't want a vec (#98547)
Adopt `join` over `try_join` in a number of places to avoid temporary Vec allocations `try_join` always copies a `Vec<Result<T>>` to a `Result<Vec<T>>` which is sometimes convenient but at least sometimes wasteful if you are immediately transforming into another collection type. Adopt it to turn `primary_modules` into a `SmallVec` since in the vast majority of cases there is only one module and so we can save a bunch of allocations of tiny arrays --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
000390a8fc |
fix: detect proxy.ts correctly with compound pageExtensions (#93246)
## Maintainer status - Current with `canary` as of 2026-05-18; this branch includes a clean merge from latest `origin/canary`. - Lightweight checks pass; full fork workflows are still `action_required` until a maintainer approves CI. - No unresolved review threads or failing jobs are reported after the refresh. - Review focus: shared convention-file basename extraction for proxy, middleware, and instrumentation under compound `pageExtensions`; TS and Rust paths use the same rule. --- ## Summary Fixes #85648 Fixes #86303 Fixes #91600 Fixes #85646 Related to #86122 Fixes #92342 Closes #92934 When `pageExtensions` is set to compound extensions like `['page.ts', 'page.tsx']`, proxy files must be named `proxy.page.ts`. However, the proxy detection logic used `file_stem()` (Rust/Turbopack) and `path.parse().name` (JS/webpack), both of which only strip the **last** extension — so `proxy.page.ts` becomes `proxy.page` instead of `proxy`, and the proxy is never detected. **Root cause:** `path.parse('proxy.page.ts').name` returns `'proxy.page'`, not `'proxy'`. Same issue with Rust's `file_stem()` which uses `rsplit_once('.')`. **Fix:** Use `file_name().split('.')[0]` (JS) / `file_name().split('.').next()` (Rust) to extract the first segment before any dot. This correctly returns `'proxy'` for both `proxy.ts` and `proxy.page.ts`. The same `fileBaseName` extraction is also used for `middleware` and `instrumentation` convention file detection, fixing compound pageExtensions for those as well. ### Changes - **Turbopack (Rust):** `crates/next-api/src/project.rs` (2 locations) + `crates/next-api/src/middleware.rs` (1 location) - **Webpack (JS):** `packages/next/src/build/index.ts` (build-time detection) + `packages/next/src/server/lib/router-utils/setup-dev-bundler.ts` (dev-time detection) - **E2e tests:** - `proxy-page-extensions/` — proxy + instrumentation with compound extensions - `middleware-page-extensions/` — middleware with compound extensions (separate fixture because the build refuses both `proxy.*` and `middleware.*` simultaneously) ### Verified locally | Mode | Bundler | Result | |------|---------|--------| | Dev | Turbopack | PASS (5/5) | | Dev | Webpack | PASS (5/5) | | Production (build+start) | Turbopack | PASS (5/5) | | Production (build+start) | Webpack | PASS (5/5) | Existing proxy test suites also verified (proxy-runtime-nodejs, proxy-with-middleware, proxy-missing-export, proxy-runtime) — no regressions. ## Test plan - [x] `proxy-page-extensions.test.ts` covers proxy header injection, page render through proxy, and `instrumentation.page.ts:register()` running - [x] `middleware-page-extensions.test.ts` covers middleware header injection and page render through middleware - [x] All 5 cases pass in dev/turbopack, dev/webpack, start/turbopack, start/webpack - [x] Existing proxy-runtime-nodejs tests pass (dev/webpack, dev/turbopack, production/webpack) - [ ] CI passes on all existing proxy tests <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
39f5d797ab |
Optimize strict module factory chunks (#98421)
### What? Reduce Turbopack ECMAScript chunk size by emitting strict module factories in a shared strict-mode scope instead of repeating `"use strict"` in every factory. The chunk format now chooses among the existing flat representation, a mixed representation with flat non-strict factories plus a nested strict array, and an all-strict representation that wraps the complete chunk in a strict IIFE. Each factory is generated through the existing single code path; the production minimizer removes its redundant directive when the factory is created inside a strict wrapper. Browser and Node.js emitters share the mode selection and serialization implementation, while the runtime remains compatible with the existing flat format. ### Why? ECMAScript modules are always strict, so large chunks currently repeat the same directive across many factories. Moving strictness to the scope where those factories are created removes redundant bytes without copying arrays or changing strict/sloppy execution semantics. For `bench/basic-app`, this reduces total raw JavaScript by 16,831 bytes (0.161%). Partitioning factories changes compression behavior, resulting in a 1,286-byte (0.051%) gzip increase across all JavaScript; format selection therefore remains based on emitted raw bytes. ### How? - Determine each chunk item's strictness from the already-collected chunk metadata. - Generate every factory once with its normal directive, then rely on the minimizer to remove redundant directives inside strict wrappers. - Compare the expected minified directive bytes removed with the exact wrapper bytes added and keep small chunks in the existing flat format. - Keep non-strict factories flat in mixed chunks and append strict factories as an array produced by a strict IIFE. - Wrap the complete registration/export in a strict IIFE when every factory is strict, keeping those factories flat and avoiding a nested array entirely. - Use the shorter arrow IIFE when the target supports it, with a function IIFE fallback for older targets. - Preserve source-map sections, scope-hoisted module IDs, factory naming, and ordering within each partition. - Add focused strict, sloppy, mixed, all-strict, old-target, and below-threshold coverage and update affected Turbopack snapshots. ### Verification - `cargo nextest run -p turbopack-tests -E 'test(snapshot)'` - `cargo clippy -p turbopack-ecmascript -p turbopack-browser -p turbopack-nodejs` - `pnpm build-all` - `pnpm --dir turbopack/crates/turbopack-ecmascript-runtime/js check:nodejs` - `bench/basic-app` production Turbopack build, repeated to confirm deterministic size totals <!-- NEXT_JS_LLM --> <!-- fleet 528ffe69-eb27-466b-a132-842272a782cf --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
f41441a0c8 |
Durable use cache: optimize env var existence checks (#98504)
We now separately track code that accesses non-inlined env vars at runtime in two categories - (existing) actual reads, the full value is accessed - (new) only unset/falsy/truthy is read at runtime For the second case, we only need to invalidate use-cache functions when the given env var transitioned betwen unset/falsy/truthy. But transitioning between two different truthy values doesn't caused invalidation. |
||
|
|
d155ba9ebf |
[turbopack] Lazily compile dynamic imports in development (client side) (#97203)
## Summary Defer compiling client-side dynamic import targets in Turbopack development until the browser requests their manifest chunk. This avoids compiling untouched dynamic imports while preserving server-side imports, CSS loading, Server Actions, source maps, and Fast Refresh behavior. One caveat on this is next/dynamic still does some eagerness. I looked a bit at changing things. But it breaks some of the guarantees there and decided to leave it off the table. --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
c1156571cd |
Turbopack: Deduplicate project options documentation (#98465)
By documenting both the partial and non-partial versions of these objects, we were documenting everything multiple times. Just reference the non-partial object from the partial object's documentation. |
||
|
|
3c8e9c3855 |
Support durable use-cache entries with client components (#98140)
The current setup is: 1. A use-cache function returns an RSC value that may contain client reference proxies. Flight serializes each reference using the client module `id`, export `name`, client `chunk` list, and `async`. The resulting Flight payload is stored in the cache entry. 2. When decoding the cache entry, the client module `id` is resolved through `rscModuleMapping` to obtain the corresponding RSC module `id` and `export`. Cache decoding does not need the client chunk list because it does not load client chunks. The outer RSC serialization emits the client reference again using the current client reference manifest, including the current chunks. We can therefore serialize cached references with an empty chunk list. The client module `id` is also unstable across builds. Instead, cache entries can store the stable client reference name. When decoding, we resolve: client reference name -> current client module ID -> current RSC module ID This makes cached client references independent of build-specific module IDs and chunk filenames. |
||
|
|
49dc4c84b4 |
Turbopack: fix output tracing include glob traversal (#98382)
### What? Make Turbopack's `outputFileTracingIncludes` file patterns root-relative and targeted during output tracing. Reject unanchored partial globs when code attempts directory-prefix matching. Add production coverage using a real pnpm-installed WASM package, plus unit coverage for anchored directory pruning and include-pattern expansion. ### Why? Turbopack previously treated include file patterns as unanchored partial matches. The directory-prefix matcher could therefore accept every descendant after matching the first literal segment: an exact WASM include under `node_modules/.pnpm` caused 2,093 `read_glob_inner` executions instead of traversing only its 5 directory prefixes. Unanchored patterns also cannot soundly answer whether a directory may contain a future match, so allowing `contains: true` with directory-prefix matching risks both over-traversal and missed files. Include behavior also differed from webpack and the documented project-root-relative semantics. ### How? Build anchored alternatives for each include pattern: one for the direct match and one for descendants of a matched directory. Anchoring lets `read_glob` prune unrelated directories, while the recursive alternative preserves existing behavior for matched directories and directory symlinks, including their resolved target contents. `can_match_in_directory` now fails immediately for `contains: true`, while direct partial matching remains supported. Route-key and exclusion matching continue to use direct partial matches and are unchanged. The production fixture resolves `lightningcss-wasm/lightningcss_node.wasm` from a real `lightningcss-wasm@1.28.2` dependency, avoiding assumptions about pnpm's store naming or patch hashes. ### Verification - `CARGO_INCREMENTAL=0 pnpm build-all` - `cargo fmt -p turbo-tasks-fs -p next-api -- --check` - `cargo test -p turbo-tasks-fs glob` (84 passed) - `cargo test -p next-api` (17 passed) - `pnpm test-start-turbo test/production/app-dir/output-file-tracing-includes-read-glob/output-file-tracing-includes-read-glob.test.ts test/production/build-trace-extra-entries-turbo/build-trace-extra-entries-turbo.test.ts test/production/build-trace-extra-entries-monorepo/build-trace-extra-entries-monorepo.test.ts` (3 passed) - `pnpm test-start-webpack test/production/app-dir/output-file-tracing-includes-read-glob/output-file-tracing-includes-read-glob.test.ts` - The shared fixture asserts that both bundlers include a root-relative exact match and exclude a nested file with the same suffix - Measured the exact reproduction at 5 include-specific `read_glob_inner` executions, down from 2,093 <!-- NEXT_JS_LLM --> <!-- fleet b3cefb44-e1ce-4bd6-83a9-da2f22848cad --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
97e7d901c8 |
turbo-tasks: explicit GC root anchoring + cross-session orphan reclamation (#96857)
### What? Reworks how the reference-counting GC decides what is a **root** (a task that must not be collected), and adds a durable path to reclaim cross-session orphaned roots Currently GC roots are persisted and the only way to get collected is for another session to revive them, and then for their ref-counts to drop. This provides a new mechanism. 1. **In-session** — Top level operations get reference counted so they can be dropped in session. 2. **Cross-session** — live roots are recorded along with a simple aging mechanism so they can still be collected even if a later session never restores it. ### Why? This closes the final GC gap. Top level operations are ambiguous currently and need tracking. With cross session aging we close a basic gap where if you delete a route between sessions we never clean up the tasks. Now it will eventually age out. ### How? **Roots are anchored explicitly**. Via pins and RAII guards like `GcRoot` **Persisted roots set + TTL age-out** A new Infra-keyspace `GcRoots` entry persists `Vec<(TaskId, ttl)>`. That allows us to detect when roots are stale and can be dropped. **Dispose transient root tasks on drop.** `RootTask::Drop` now disposes tasks and cleans up references allowing the GC to work |
||
|
|
8b9cdc5eef |
perf(turbopack): skip ignored files during server tracing (#97475)
So I was playing around with profiling some very simple next apps when I noticed this. Basically, because of how things were set up we were spending a good chunk of our build time analyzing files we were going to reject. I went around and around trying different combinations to make sure we ignore those files (and only the files we should ignore) to speed up build times. Since it is a constant impact, mostly on small projects, but on some, it can make a big difference. I had some adversarial rounds with agents to see if they can break the config in the ways the old comments hints at why we didn't do that. And this final round finally couldn't get it to break. For a v0-generated Hello World app, compile time is 28.0% faster (+/-1.7%) and a full clean build is 15.9% faster (+/- 1.2%). --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
ec107c16dd |
lazy server hmr (#96566)
## Summary Make Turbopack server HMR demand-driven. Server updates are now compiled and applied when the next relevant request writes an endpoint, instead of eagerly evaluating changed server modules after every file change. This replaces the aggregate server HMR subscription with an on-demand update API, while preserving incremental updates and falling back to full cache eviction when a restart is required. Test Plan: added an e2e test <!-- NEXT_JS_LLM --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
e3790aa911 |
More granular cache keys for use-cache entries (#95233)
Caveats: - I had to skip `react[-dom][/*]` and `private-next-rsc-server-reference` and `private-next-rsc-cache-wrapper` imports in the code hash and env-var tracking. Because those all end up pulling in app-page-turbo.runtime.prod.js which reads many env vars and would cause constant deopting. But this should still be correct. The code of these imports is included via the Next.js version, and no env vars should change the semantics of any of those imports. - Static env var reads are collected, but too dynamic accesses are silently ignored and don't lead to deopts (same goes for env var reads in native NAPI addons). This means that this cache reuse is not guaranteed to be 100% guaranteed to never lead to stale caches. - Code hashing and env var collection happens on a per-module basis. So if you put all use-cache functions into a single file and/or together with react components, then you will see extraneous invalidations. This will be fixed by either generally enabling module splitting for all of Turbopack, or by adding a special transform that does it for use-cache functions. Followups: - There are some env var static analysis gaps that will be immediate followups before broader testing. These are the various TODOs added in https://github.com/vercel/next.js/pull/95310 - Client components invalidation is very coarse grained right now (statically imports any client component anywhere -> deopt completely). Followup for the future. But the current setup is correct. This would just improve effectiveness further - Do this in dev as well. Currently there is no NFT at all in dev (for performance reasons) - Module splitting for more granular tracking - Include entropy when serializing server reference arguments for cache key Todo: - [x] Use implementation code hash (includes inlined env vars): from #94234 - [x] Include non-inlined runtime env vars: from #95310 - [x] Include client reference manifest (very coarse for now) - [x] Include Next.js version (for wire format, etc) - [x] This is now done for all use-cache entries now. Not just for `use cache: remote`. Is that the intended behavior? Yes - [ ] ~~if `NEXT_DEPLOYMENT_ID` is in the env vars. just deopt and don't care about stringifing and hashing the env vars~~ - [x] Is cache key size a problem? Currently you can get this: (values are always hashed) `CustomCacheHandler::get ["80e6f6560092f0078775e7e787c1c10ecf6dea0bc4",[],["d984fbaa996274737f3b59345a300a20","16.4.0-canary.5","__NEXT_NO_MIDDLEWARE_URL_NORMALIZE=undefined","NEXT_OTEL_PERFORMANCE_PREFIX=undefined","__NEXT_PRIVATE_ORIGIN=a04f4b9d6a8f42724740a480e9a2bc67c053dc04377be831c0e2c407a1422004","NEXT_PRIVATE_RESPONSE_CACHE_TTL=undefined","NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE=undefined","__NEXT_CACHE_COMPONENTS=b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b","__NEXT_ROUTER_BASEPATH=undefined","__NEXT_MANUAL_CLIENT_BASE_PATH=undefined","__NEXT_INSTRUMENTATION_CLIENT_ROUTER_TRANSITION_EVENTS=undefined","__NEXT_APP_NAV_FAIL_HANDLING=undefined","__NEXT_GESTURE_TRANSITION=undefined","__NEXT_USE_OFFLINE=undefined","NEXT_DEBUG_BUILD=undefined","__NEXT_VERBOSE_LOGGING=undefined...]] [["_N_T_/layout","_N_T_/page","_N_T_/","_N_T_/index"]]` |
||
|
|
17a901a74f |
Guard filesystem reads against unresolved symlinks (#97902)
### What? Adds debug-only OS realpath validation to successful `DiskFileSystem` file and directory reads. When a successfully canonicalized path differs from the supplied path, the read returns a normal task error naming both paths. Fixes pattern/glob traversal and NFT tracing so physical filesystem access uses resolved paths while logical paths remain available for user-visible specifiers and complete symlink-chain recovery. ### Why? Reading through an unresolved symlink parent gives the same filesystem object multiple path identities. That can make Turbo Tasks dependency tracking and invalidation inconsistent and can produce invalid deployment ZIPs when NFT output contains files below unresolved links. The checks return errors rather than asserting because paths can disagree temporarily under eventual consistency. Propagating a task error avoids panicking a worker thread while still exposing invalid callers during development. ### How? The validation lives directly in `DiskFileSystem::read` and `DiskFileSystem::raw_read_dir`. It calls the OS canonicalization API inline instead of the Turbo Tasks realpath task, keeping the diagnostic out of the task dependency graph. The guard runs only after the OS read succeeds, so missing/non-directory probes preserve their existing behavior. `read_matches` resolves each physical directory immediately before enumeration while retaining logical `PatternMatch` paths. `read_glob` and `track_glob` now resolve their initial directory before enumeration. Symlinks discovered later through wildcard segments are also traversed through resolved targets. `ReadGlobResult` deliberately retains logical paths rooted at the supplied base, allowing consumers to call `realpath_with_links` and recover the complete symlink chain. Consumers follow that contract explicitly: - NFT includes expand each logical match with `realpath_with_links`, emit resolved files and every traversed symlink, skip resolved directory targets, and deterministically deduplicate/sort output. - `import.meta.glob` uses recursive logical keys as the source of user-visible requests, while module resolution follows and tracks symlinks. - the hash-glob example resolves returned logical paths before reading. Webpack-loader context dependencies are covered for both `path/to/symlink/inner/path/*` and `path/to/*/inner/path/*`. The loader fixture performs its directory read with Node `fs`, reports the directory using `addContextDependency`, and Turbopack tracks the resolved target. ### Verification - `cargo fmt -p turbo-tasks-fs -p turbopack-ecmascript -p next-api -- --check` - `cargo clippy -p turbo-tasks-fs -p turbopack-ecmascript -p next-api --all-targets` - `cargo test -p turbo-tasks-fs` (128 passed) - `cargo test -p next-api` (7 passed) - `cargo check -p turbo-tasks-fs --examples` - `import.meta.glob` symlink execution fixture (1 passed) - Nine targeted node-file-trace CI cases with `release-with-assertions` (9 passed) - `pnpm build-all` - `webpack-loader-fs` Turbopack dev e2e (1 passed) - `build-trace-extra-entries-turbo` Turbopack production e2e (1 passed) - twoslash Turbopack production, normal mode (4 passed) - twoslash Turbopack production, cache-components mode (4 passed) - `bench/heavy-npm-deps` Turbopack development smoke test (HTTP 200) ### Notes The disk guard is cross-platform, while its symlink-parent regression test is Unix-only, matching neighbouring symlink tests. On Windows, OS canonicalization can also normalize casing and 8.3 short names; a debug read using a non-canonical spelling will therefore return the same diagnostic error. <!-- NEXT_JS_LLM --> <!-- fleet 1d32e12c-f4ec-4f22-862a-c85f0005805c --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
6d6228c0c6 |
Prune incomplete parallel route matchers (#97108)
## Summary
This adds an experimental `strictRouteMatching` flag that leaves a
matcher out when its finalized loader tree contains a synthesized
default that will always call `notFound()` for a slot actually declared
by the owning layout. Adding a `default.tsx` keeps the matcher, and
structural router-state branches that are not real slots of that layout
do not make it incomplete. The flag defaults to `false` so matcher
pruning can roll out independently from the preceding loader-tree
correction.
The main goal is to make `children` and named slots behave the same way.
If a URL cannot construct a complete route tree on its own, it should be
treated as an unmatched URL instead of sometimes showing a slot 404 and
sometimes producing a missing page or default error.
## Semantics
For example:
```text
app/split-matcher/
├── layout.tsx
├── foo/page.tsx
├── bar/page.tsx
└── @slot/[...parts]/page.tsx
```
With strict route matching disabled, Next.js emits matchers for
`/split-matcher/foo`, `/split-matcher/bar`, and the broad
`/split-matcher/[...parts]`. The broad matcher can match any URL, but
its `children` branch has neither a matching page nor a default, so it
can only construct a permanent 404 tree. With strict route matching
enabled, that broad matcher is omitted while `/foo` and `/bar` remain
because each combines a real `children` page with the named-slot
catchall. Adding `app/split-matcher/default.tsx` deliberately makes the
broad matcher complete and keeps it.
The same rule applies without a catchall:
```text
app/disagreeing-slots/
├── layout.tsx
├── @first/foo/page.tsx
└── @second/bar/page.tsx
```
`/foo` is incomplete because `@second` has no matching page or default,
and `/bar` is incomplete for the corresponding reason in `@first`, so
neither matcher is emitted. A later PR in this stack reports the
now-unreachable page files as a project misconfiguration.
A declared `children` route is treated like any named slot:
```text
app/declared-children/
├── layout.tsx
├── page.tsx
└── @panel/
├── default.tsx
└── details/page.tsx
```
`/declared-children` is complete because `children` uses `page.tsx` and
`@panel` uses its default. `/declared-children/details` is incomplete
because `@panel` matches its page while the declared `children` slot has
no matching page or default at `/details`, so that matcher is pruned.
A route composed entirely from declared named slots is also complete:
```text
app/named-only/
├── layout.tsx
├── @left/[...slug]/page.tsx
└── @right/[...slug]/page.tsx
```
The preceding PR's default-on `explicitParallelRouteChildren` behavior
means this loader tree contains only `left` and `right`. Strict matching
keeps `/named-only/[...slug]` because both declared slots match; it does
not invent `children` and then prune the route for failing to satisfy
that nonexistent slot.
We got here incrementally.
[#47872](https://github.com/vercel/next.js/pull/47872) introduced the
404 fallback for unmatched parallel slots,
[#60186](https://github.com/vercel/next.js/pull/60186) added a
development warning because this was confusing in practice and linked
[#51805](https://github.com/vercel/next.js/issues/51805) and
[#49569](https://github.com/vercel/next.js/issues/49569), and
[#84702](https://github.com/vercel/next.js/pull/84702) later made a
missing default a build error for named slots while leaving `children`
on the old fallback for backwards compatibility. Strict matching takes
the next step and treats a matcher that can only construct a permanent
404 tree as unmatched.
This changes soft navigations that only worked by preserving a
previously active slot even though the URL could not be loaded directly,
which is why matcher pruning remains behind its own experimental flag.
The interception retention markers backed by `default-null`, including
named host slots from the first PR in this stack, are complete route
patches and are not pruned. This change however is well motivated
because if you did client nav to a route that only matches a named slot
and then hard refresh you will end up getting a 404. This is a sign our
current semantics are actually broken.
If you want to preserve the perma 404 behavior of the
slot-without-default you can just add a default and make it call
`notFound()` unconditionally.
## Verification
- `pnpm build-all`
- Focused `normalize-catchall-routes` unit coverage
- Production and development e2e coverage on Turbopack and Webpack
- The pruning e2e matrix with Cache Components enabled
<!-- NEXT_JS_LLM -->
|
||
|
|
05ed7c1be2 |
Omit undeclared children slots from app routes (#97184)
## Summary Parallel route layouts can be composed entirely from named slots, but loader tree construction currently synthesizes a `children` fallback whenever any named slot exists. This makes `children` semantically required even when no page, default, or ordinary route branch declares it. This adds `experimental.explicitParallelRouteChildren` and enables it by default. When enabled, `children` is included in a layout slot set only when the filesystem declares an ordinary route that can render at that level. A layout by itself is only structure and does not declare a route target. Ordinary descendants are traced until they reach a page or default, including through deeper named slots, before they cause `children` to be included. Setting the flag to `false` temporarily restores the legacy implicit `children` fallback. This flag only controls whether `children` exists in the loader tree. It does not prune incomplete matchers; that is the separate `experimental.strictRouteMatching` behavior in the next PR. Named slots keep their existing default and soft navigation semantics. The preceding PR retains the slots owned by an interception host without treating `children` specially, so an undeclared child is no longer needed for that behavior. A real `children` branch still uses the retention marker when it is one of the host layout slots. ## Semantics For example, this layout declares only named slots: ```text app/dashboard/layout.tsx app/dashboard/@left/page.tsx app/dashboard/@right/page.tsx ``` With `explicitParallelRouteChildren` disabled, Next.js adds a synthetic `children` branch whose built-in default calls `notFound()`, even though the layout never declared or rendered it. With the default behavior enabled, the loader tree contains only `left` and `right`, so `/dashboard` is matched from the route targets that actually exist. This is different from an ordinary branch whose route targets are deeper in the tree: ```text app/nested/layout.tsx app/nested/@sidebar/[...slug]/page.tsx app/nested/content/layout.tsx app/nested/content/@left/[...slug]/page.tsx app/nested/content/@right/[...slug]/page.tsx ``` Here `content` really is the `children` branch of `nested`. The scan follows `content` through its layout and deeper named slots, so `children` remains required. `/nested/content/anything` can construct every declared slot, while `/nested/incomplete` only matches `sidebar` and is still incomplete. The distinction is whether the ordinary descendant eventually reaches a page or default, not whether a layout happens to exist along the way. The focused children detection coverage proves that a layout-only descendant does not synthesize `children`, while an ordinary branch whose route targets live inside deeper named slots still does. The limitation coverage also proves that named-only trees render pages, CSS, metadata, and regular error boundaries. It intentionally asserts the current broken behavior for HTTP access fallbacks and metadata or viewport failures so those expectations can be flipped when renderer ownership no longer depends on `children`. ## Verification - `pnpm build-all` - Turbopack and webpack development and production coverage for `explicit-parallel-route-children-detection` - Turbopack and webpack production coverage for `interception-dynamic-segment`, `parallel-routes-layouts`, and `explicit-parallel-route-children-legacy` - The same existing production coverage with Cache Components enabled - Turbopack and webpack production coverage for the documented named-only limitations <!-- NEXT_JS_LLM --> |
||
|
|
286fcc346f |
Turbopack: call loadActionManifest for app-route (#97921)
For #95233, I need to read server-reference-manifest.json even for App Routes, which previously didn't emit server action manifest entries at all (because thus far, it was unnecessary). |
||
|
|
319cdf2bc1 |
fix(turbopack-node): make process_pool inert on wasm (#97858)
> Replaces #97584, which was **not merged**. Reordering this stack briefly left that PR > pointing at a base branch that had come to contain its own head commit, so GitHub closed it > as merged and deleted its branch. Nothing from it reached `canary`. It had been approved; > this PR is the same commit (`fb0b97fd84`), restored, and needs review again. Sorry for the churn. ### What? `turbopack-node`'s `process_pool` feature is inert on wasm, leaving `worker_pool` as the only Node backend there. ### Why? The child-process pool needs `tokio::process` and a TCP listener, neither of which exists on wasi: ``` error[E0432]: unresolved import `tokio::process` # gated #[cfg(not(target_os = "wasi"))] in tokio error[E0599]: no `TcpListener::bind` on wasi --> turbopack/crates/turbopack-node/src/process_pool/mod.rs:315 ``` Turning the feature off from the outside is not possible: `process_pool` is a **default** feature of both `turbopack-node` *and* `next-core`, so `--no-default-features` at the top level does not suppress it. `worker_pool` — Node worker threads over napi — is already a first-class alternative selected by `TurbopackPluginRuntimeStrategy`, so no new mechanism is needed. ### How? Gate the seven `process_pool` sites on `not(target_family = "wasm")`: the module, the sealed backend impl, the constructor, the config enum variant, the default-strategy selection, and the `next-api` import and match arm. Host feature semantics are unchanged. <!-- NEXT_JS_LLM --> <!-- fleet b6d0486f-97c7-42a7-bdaf-3490774cdec3 --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
ef85120648 |
Turbopack: Improve file existence error handling in realpath_with_links and in module resolution (#97717)
Fixes some regressions from
https://github.com/vercel/next.js/pull/97395.
Human summary of changes:
- Plainly return `NotFound` for `realpath_with_link` whenever any step
in the resolution algorithm fails, matching the behavior the libc uses.
- When probing in the module resolution algorithm, explicitly check for
`NotFound`.
- Fully remove the extremely non-standard and confusing behavior of
trying to return the last processed path from `realpath_with_links` if a
failure occurs.
- Modify `realpath` to return a `RealPathError` instead of just an
`anyhow::Error`, so that callers that care about the actual error kind
but not all of the intermediate links can still use it instead of
`realpath_with_links`.
- Rename `RealPathResultError` to `RealPathError`, and make it actually
implement `Display` so that it's easier to coerce to an `anyhow::Error`.
This requires including a tiny bit more data on this object, but in the
hot path it's just one extra clone of a `FileSystemPath`.
Human discussion:
https://vercel.slack.com/archives/C09R44U5HQW/p1787322213695299
AI slop summary of why #97395 caused a regression:
> The regression came from newly added missing-target detection in
|
||
|
|
a11f82c557 |
[turbopack] defer NFT module content hashes (#97773)
### What? Separate trace-graph module path collection from module content hashing. Exclusion filtering now requests only cached module identifiers, while full NFT data still layers content hashes on top when they are required. ### Why? Applying output-file-tracing exclusion globs needs module paths but not content hashes. Keeping these computations separate avoids requesting full-graph hashes solely to decide which modules should be skipped. This is an inspection experiment, not a demonstrated performance improvement. A seven-sample release A/B left the median compilation phase unchanged at 2700 ms. ### How? A private identifier-only graph task owns the existing DFS traversal. `traced_module_data_for_graph` reuses those identifiers and computes hashes for its full result. `traced_modules_for_entries` drops the unused hash-salt input and consumes only identifiers for glob matching. ### Verification - `cargo fmt -- --check` - `cargo check -p next-api` - `next-server.js.nft.json`: byte-identical SHA-256 `da4c5a494efbbb38eb966066115de96adb5d0ec693fba17bbed669c99cafae43` - `next-minimal-server.js.nft.json`: byte-identical SHA-256 `7c58d40dc2ecbe6ddd26dd0c602467d80f1a8ea3320e2c6e8e5f4d6be14156cd` - Not run: clippy, Rust test suite, or integration tests (inspection experiment) <!-- NEXT_JS_LLM --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
4c4d523084 |
Migrate remaining async blocks to async closures (#97701)
**View diff without whitespaces** Fix remaining cases of https://github.com/vercel/next.js/pull/97666 - Mostly replacing async blocks with async closures - And second commit: some more making closures sync where possible |
||
|
|
9fbec9300f |
Migrate some async blocks to async closures (#97666)
This is everything except `turbo-tasks-[backend]`
Using ast-grep replacement:
- search: `|$$$ARGS| async move {$$$BODY}`
- replace: `async |$$$ARGS| {$$$BODY}`
This also made it obvious in some places that we don't actually need
async-await,
e.g. `.map(async |v| v.to_resolved.await)`
|
||
|
|
6c0dd8400b |
Turbopack: deduplicate Pages Router app chunks (#97664)
Previously, Turbopack treated `_app` and Pages router pages as completely separate. So using code both in `_app` and `pages/foo.tsx` would lead to a lot of duplicated code to be loaded at runtime. But in reality, `_app` is always loaded for Pages, so we can thread the availability info and skip chunking modules that were already loaded by `_app`. Recreation of https://github.com/vercel/next.js/pull/97549 Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> |
||
|
|
e2fb664ceb |
Remove HmrTarget (#97253)
With #94948 we intended to move the client over to the firehose feed of HMR events with the intent of unifying the code paths for maintenance. However, now that Server HMR is moving to a pull-based model (which client HMR will not be able to implement), let's keep the split. There's no need to encode the HmrTarget into each surface, and we can just use the function name to indicate which mode of HMR it's for. |
||
|
|
1a304e3710 |
Keep HMR instructions typed until serialization (#96569)
Stacked on #96686. ## What? Keep HMR update instructions as typed Rust values until they cross the client protocol serialization boundary. The JSON wire format remains unchanged. ## Why? HMR aggregation currently operates on `serde_json::Value`, so it has to inspect serialized `type` fields and clone values out of JSON objects. This loses type information before aggregation and makes unsupported instruction variants easy to overlook. Keeping instructions typed lets aggregation use concrete equality and hashing, preserves stable ordering while deduplicating updates, and falls back to a total update when it encounters an instruction type it cannot aggregate. ## How? - Add `UpdateInstructionValue`, a type-erased serializable value with typed downcasting and equality. - Represent chunk-list and merged ECMAScript updates with owned, hashable values backed by frozen collections. - Aggregate typed instructions with first-seen ordering for merged updates and last-writer-wins values for chunk updates. - Serialize instructions only when producing the HMR protocol message. - Add unit coverage for downcasting, equality, hashing, aggregation behavior, and the existing JSON protocol shape. ## Verification - Unit tests cover the new typed values and aggregation behavior. - Serialization tests confirm the client-facing HMR messages are unchanged. - CI <!-- NEXT_JS_LLM --> |
||
|
|
b677feb02f |
Turbopack: More aggressively debounce filesystem watch events if we detected changes to node_modules (#96116)
Previously, we were debouncing update by sleeping 1ms at a time on macos and windows, and 10ms at a time on Linux. During a slow `pnpm install`, or a `git checkout`, this could cause us to do a bunch of extra throwaway work. Changes: - Increase the debounce interval to a consistent 10ms everywhere. This should still be small enough that it's not noticable on macos or windows. - If an event touches `node_modules`, there's a good chance that a package manager is running and many other files will be modified, so extend the batch deadline by 200ms instead of 10ms. - Because there's a chance that the batch deadline could get extended indefinitely (this was always possible, just more likely now) include a compilation event that gets logged after 5 seconds. |
||
|
|
da50acde10 |
Turbopack: gracefully handle outputFileTracingIncludes matching a symlink (#97507)
Closes https://github.com/vercel/next.js/pull/96999 Make sure we don't do `.read().hash()` which is incorrect with symlinks. Instead, hash the symlink itself instead of its target. This is what we copy into the function source anyway --------- Co-authored-by: vercel-fleet[bot] <308483924+vercel-fleet[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
c7b87c2329 |
Emit whole-app server NFTs when output: 'standalone' is used with an adapter (#97287)
|
||
|
|
d0798a0416 |
Fix missing Pages runtime in adapter Pages API outputs (#96819)
## Summary ### Original issue Pages API functions produced through a build adapter can fail during function initialization, before the customer handler executes: ``` Cannot find module 'next/dist/compiled/next-server/pages-turbo.runtime.prod.js' ``` The failure is triggered when an externalized dependency used by the API route imports a Next.js module such as `next/head`. At runtime the load path is: ``` external dependency -> next/head -> head-manager-context.shared-runtime -> Pages vendored head-manager context -> pages/module.compiled.js -> pages[-turbo].runtime.prod.js ``` The two important edges at the end of this chain are selected dynamically. The Next.js require hook redirects the shared-runtime import to the Pages vendored context, and `pages/module.compiled.js` selects the bundler-specific production renderer. As a result, a Pages API entry trace does not discover the regular Pages renderer. It naturally contains `pages-api[-turbo].runtime.prod.js`, which is the runtime for the API route module, but not `pages[-turbo].runtime.prod.js`, which is reached through the external `next/head` import. This is why the failure is specific to Pages API routes. A regular Pages SSR entry already references and traces the Pages renderer. App Router entries use different route modules and traces. ### Fix Trace the hidden Pages renderer dependency using the mechanism appropriate to each bundler: - **Turbopack:** add `pages-turbo.runtime.prod.js` as an explicit entry in `Project::pages_traced_modules`. The existing native Turbopack module graph then traces its full runtime closure. This does not run Node File Trace and remains scoped to Pages endpoints. - **Webpack:** run the existing Node File Trace path on `pages.runtime.prod.js` and merge that closure into the Pages shared assets. This code remains inside the non-Turbopack branch. The existing require-hook modules remain separate, and App Router or neutral endpoint trace sets are unchanged. No adapter change is required. ### Regression test The fixture externalizes a package that imports `next/head`, matching the reported dependency boundary. The test builds with an adapter, materializes only the generated Pages API function entry and its provided assets into a clean directory, initializes the production environment, and requires the handler in a child process. This verifies the observable initialization behavior under both Turbopack and Webpack rather than asserting a particular output file list. ## Verification - `cargo check -p next-api` - `pnpm --filter=next build` - `pnpm test-start-turbo test/production/adapter-pages-api-runtime/adapter-pages-api-runtime.test.ts` - `pnpm test-start-webpack test/production/adapter-pages-api-runtime/adapter-pages-api-runtime.test.ts` <!-- NEXT_JS_LLM --> Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
13491b0d08 |
Stop serializaing hmr version state (#97146)
Mark VersionedContentMap as `serialization = "none"` This will cause every dev session to start with an empty map, to ensure _derived_ data from the old map doesn't survive, the functions interacting with the state are all marked `session_dependent`. This is directly motivated by the work on GC which runs into problems with the State objects storing operations but not consistently `connect`ing to them on cache hits, which causes the GC to drop them. Additionally, this prevents eagerly recomputing all server hmr routes on startup. In theory this will slow down reconnecting a dev session, but the underlying operations are still cached and the tasks computing Versions, so it just the map that needs to be reconstructed, so the cost should be minor. In fact i would guess that it is a bit faster to reconstruct this map than to restore all the keys from disk in the case where the map is large. <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors We encourage you to use AI to assist you in researching, creating, and reviewing changes. However, you must review and deeply understand the contributions you are making. For this reason, **pull request descriptions from external contributors must be written by a human**. ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Signed commits - This repository requires verified commit signatures on protected branches. - If this pull request is blocked for unsigned commits, re-sign the commits and force-push the branch. - A `Signed-off-by` line in the commit message is not enough. ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # --> --- <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub> |
||
|
|
cb36e1d594 |
Fix debug build paths Pages Router support entries (#93529)
## Summary - Preserve Pages Router support entries during Turbopack `--debug-build-paths` selective builds - Include custom Pages Router `404` and `500` entries alongside `_app`, `_document`, and `_error` when a Pages route is selected - Add regression coverage for selected Pages routes retaining support entries in `pages-manifest.json` ## Testing - `RUSTC="$(rustup which --toolchain nightly-2026-06-24 rustc)" cargo check -p next-api` - `RUSTC="$(rustup which --toolchain nightly-2026-06-24 rustc)" pnpm --dir packages/next-swc build-native` - `HEADLESS=true pnpm test-start-turbo test/production/debug-build-path/debug-build-paths.test.ts -t "single page with pages/ prefix"` - `HEADLESS=true pnpm test-start-turbo test/production/debug-build-path/debug-build-paths.test.ts` Fixes #93515 <!-- NEXT_JS_LLM_PR --> |
||
|
|
5e8f31f7bd |
Turbopack: Improve how DiskWatcher is configured and fix polling watcher bugs (#96440)
- Adds a `DiskWatcherConfig` object to centralize the recursive mode, polling, and invalidation reason options. - **Polling Fix:** Use the non-recursive mode if the polling watcher is selected, ignoring the OS/platform. - **Polling Fix:** Treat mtime events as data update events if the polling watcher is selected (this is what https://github.com/vercel/next.js/pull/96288 was doing, but this PR only does it if polling is enabled) - Only treat the `MetadataKind::Any` event as a data change on macOS. Fixes #80665 Closes #96288 |
||
|
|
10dc5fd5c5 |
[turbopack] Support experimental.serverMinification & expand experimental.turbopackMinify (#96578)
Closes https://github.com/vercel/next.js/issues/96574. Implements: > experimental.turbopackMinify accepts per-environment granularity, e.g. { server: false, client: true } (restoring serverMinification parity) I also added the edge runtime in there, even though it isn't too widely used. |
||
|
|
2049354a66 |
Fix missing styled-jsx styles in Pages Router SSR on adapter builds (#96632)
### What?
Fixes styled-jsx styles being missing from the server-rendered HTML of
Pages
Router apps built through a build adapter (what a Vercel deployment
uses),
when the app has its own `styled-jsx` dependency.
- `crates/next-api/src/next_server_nft.rs`: extracts
`styled_jsx_require_hook_modules()` and adds an accurate comment to the
`is_using_adapter` early return (it no longer claims no NFT tracing is
needed - see "Why?").
- `crates/next-api/src/project.rs`: `Project::additional_traced_modules`
now
also returns those modules, alongside the existing `cacheHandler`/
`cacheHandlers` entries.
- `packages/next/src/server/require-hook.ts`: extracts
`styledJsxRequireHookEntries()` (the JS-side equivalent, used by
webpack)
and replaces the silent `catch (_) {}` around registering the aliases
with
a diagnostic warning (still never throws).
- `packages/next/src/build/adapter/build-complete.ts`,
`packages/next/src/build/collect-build-traces.ts`: use that helper
instead
of re-deriving the same resolution inline. No behavior change for
webpack.
- `test/production/adapter-styled-jsx/`: new regression test.
### Why?
The Pages Router renderer and user code have to share a single
`styled-jsx`
module instance: `render.tsx` creates the style registry and hands it to
user code through a React context owned by that specific module
instance. If
user code ends up with a *different* `styled-jsx` instance - which
happens as
soon as the app depends on a `styled-jsx` version that doesn't dedupe
with
Next.js' own pinned version - `JSXStyle` silently renders nothing during
SSR
(`if (!registry) return null`). The `jsx-*` class names are still
emitted by
the transform, so the only visible symptom is a flash of unstyled
content:
the CSS only gets inserted client-side after hydration.
Turbopack keeps `styled-jsx`/`styled-jsx/style` external in the pages
server
bundle, so the single-instance guarantee is established at **runtime**
by
`next/dist/server/require-hook`, which resolves those requests to
Next.js'
own copy from its own install location via a plain `require.resolve()`.
Nothing in any module graph references the files that resolves to (user
code
only ever references its own copy), so output tracing has to add them
explicitly, or the deployment is missing the file, the hook's
`require.resolve()` throws, the aliases are (silently, since #89402)
never
registered, and the two `styled-jsx` instances drift apart.
That explicit tracing existed only for the whole-app
`next-server.js.nft.json` / `next-minimal-server.js.nft.json` - and
those are
not generated at all when a build adapter is used, because an adapter
assembles its output from each endpoint's own NFT instead
(`is_using_adapter` early return in `next_server_nft_assets`). Its
comment
said adapters "don't need any server NFTs" - true for those two
whole-app
files, but not for the styled-jsx entries they also carried, which had
no
other way into an adapter build. Reproduced against a published
`next@16.3.0` release: a plain `next build` with an adapter never
shipped
`node_modules/next/node_modules/styled-jsx/style.js`, nothing deleted or
hand-edited.
### How?
`Project::additional_traced_modules` is Turbopack's existing mechanism
for
"trace this into every endpoint even though nothing references it" - it
already carries `nextConfig.cacheHandler`/`cacheHandlers` for exactly
the
same reason (a runtime-only dependency invisible to static analysis).
Adding the require hook's styled-jsx modules there gets them into every
endpoint's own `*.nft.json` via the existing `trace_endpoint` plumbing,
which
`build-complete.ts` already loads unconditionally for both bundlers - so
no
TypeScript change is needed on the Turbopack side at all.
`styled_jsx_require_hook_modules()` centralizes the resolution (used by
both
`additional_traced_modules` and the pre-existing whole-app NFT, which
still
needs it independently for `output: 'standalone'`).
Webpack has no equivalent Rust-side tracing, so `build-complete.ts`
keeps
tracing these itself via `nodeFileTrace`, now through the shared
`styledJsxRequireHookEntries()` TS helper instead of re-deriving the
same
resolution inline.
The new test builds a Pages Router page with `<style jsx>` through a
build
adapter fixture (same shape as `test/production/adapter-root`), with a
`styled-jsx` dependency version that doesn't dedupe with Next.js' pinned
version, and asserts the adapter's `onBuildComplete` output declares
every
file the require hook resolves.
Verified end-to-end against a published `next@16.3.0` install (the
sandbox
can't build the native Turbopack addon):
- an existing, unmodified mechanism (`cacheHandler`) was used to confirm
the
endpoint-NFT -> adapter-assets pipeline this fix relies on actually
carries
a traced module through to the deployment;
- the Rust change's exact output was simulated at the point
`build-complete`
loads each endpoint's NFT, and the SSR HTML went from 0 `<style
id="__jsx-...">` tags (with `jsx-*` classes still present - the reported
flash) to the expected styled-jsx CSS being present;
- reverting the simulation reproduces the failure again;
- webpack output (`next/node_modules/styled-jsx/{index,style}.js`,
`node-environment`, `require-hook`) is unaffected.
- `cargo check` / `cargo clippy` / `rustfmt --check` pass on the Rust
change.
- [x] Tests added (`test/production/adapter-styled-jsx/`)
- [x] Errors have a helpful link attached — n/a, no user-facing error
added (warning only)
---------
Co-authored-by: vercel-fleet[bot] <308483924+vercel-fleet[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
|
||
|
|
a75ece16b5 |
Turbopack: don't strip async-module runtime from shared runtime chunks (#96599)
### What? Turbopack could emit a `[turbopack]_runtime.js` without the async-module (top-level await) machinery while chunks written next to it call `__turbopack_context__.a(...)`, failing production builds with `TypeError: __turbopack_context__.a is not a function`. Reported against 16.3 when loading a `postcss.config.js`. Intermittent, production only. ### Why? The runtime chunk is emitted to a fixed path, so every module graph using a chunking context writes the same file. Since #94376 the async-module machinery is dropped when a graph has no async modules — but that's decided per graph. The Next.js node execution context is shared by every build-time JS evaluation (postcss configs, webpack loaders, `next/font/google`), each with its own `ModuleGraph`. A graph with no async modules emits a runtime without `.a` and clobbers the variant the postcss loader needs. The winner is `assets.first()` in `emit_assets`, which depends on emission order — hence the intermittency. ### How? Add `shared_runtime_chunk` to the chunking contexts and set it where one runtime is shared by several graphs, so those always emit the complete runtime. This matches the carve-out that already exists for development: in both cases a single graph can't see everything sharing the runtime. Whole-app server contexts keep the optimization. The browser side tested `RuntimeType::Development` as a proxy for per-page graphs, which only works because `per_page_module_graph` currently *is* `mode == Development`. It now reads the real flag. Also renames `has_async_modules` to `include_async_module_runtime`, since it's now true whenever we can't tell. I audited the rest of `.next/build/` for the same hazard: the runtime chunk (and its `.map`) was the only fixed-path output. Everything else is content-hashed via `AssetIdent::output_name`. ### Testing `test/production/app-dir/turbopack-shared-runtime-async-module` uses a single synchronous loader — enough on its own to produce the stripped runtime. Verified to fail without the fix and pass with it. `cargo test -p turbopack-tests` passes with no snapshot churn. Follow-up, not in this PR: the conflict is silent. `EmitConflictIssue` is `IssueSeverity::Error` but never fired here, because the two runtimes weren't grouped into one `emit_assets` call. |
||
|
|
b4e3fecc79 |
[turbopack] add experimental.turbopackChunking config (#96398)
Built using @sokra's Fleet! This re-arranges all of our chunking experimental features under `experimental.turbopackChunking` and adds the following options: ```javascript /** * Avoid creating more than one chunk smaller than this size, in bytes. Smaller * chunks are merged into bigger ones to avoid that. Defaults to `50000` (50 KB). */ minChunkSize?: number /** * Avoid creating more than this number of chunks per chunk group. Chunks are * merged into bigger ones to avoid that. Defaults to `40`. */ maxChunkCountPerGroup?: number /** * Never merge chunks bigger than this size, in bytes, with other chunks. This keeps code * in big chunks from being duplicated across multiple chunks. Defaults to `200000` (200 KB). */ maxMergeChunkSize?: number /** * Minimum size, in bytes, for a component chunk to be emitted on its own when * `generateComponentChunks` is enabled. Component chunks smaller than this are folded into a * single remainder chunk. Defaults to `20000` (20 KB). */ minComponentChunkSize?: number ``` |
||
|
|
466cdfaa27 |
Turbopack server hmr: avoid complete clear on graph changes (#95546)
Currently, importing a new module causes a complete eviction (`clear()`) and re-evaluation of server chunks, both in the Turbopack runtime's module cache and Node's `require.cache`. Here's what happens today: - A new module is imported into a chunk's graph - This changes its `availability_info`, which in dev for non-entry chunks is encoded into the chunk's filepath - `VersionedContentMap` works entirely on chunk paths. When we construct instructions to transition a chunk into its new state, we fail to find the prior state and fall back to `clear()` as described above. An ideal version of this is a refactor that implements `VersionedContentMap` on a per-module basis, not a per-chunk one. This PR doesn't do that, but achieves consistent module-level updates when a chunk's availability info changes. It does this by implementing chunk lists for server entry chunks the same way the client hmr implementation does: an entry chunk's version aggregates the versions of its dependent chunks (including dynamically imported ones), keyed by merger rather than path. Entry chunks do not encode availability info in their paths, so they are insulated from missing versions. Updates are applied in Node.js through the same shared merged-update machinery in the unified hmr runtime that the client already uses, plus a new `ChunkListUpdate` branch in the server hmr client to unwrap the merged updates. Details: - The merged-update wire format (`EcmascriptMergedUpdate` etc.) moves from turbopack-browser into shared `turbopack_ecmascript::chunk_list`, along with runtime-agnostic `ChunkListVersion` and `update_chunk_list` implementations. - `turbopack-nodejs` gains a chunk content merger mirroring the browser one. `EcmascriptBuildNodeEntryChunk`'s versioned content is now a chunk-list content over its sync and async chunks. It is version-only and never emitted; the entry chunk still inlines its loader calls. - The aggregate server hmr subscription now tracks only entry chunks. Shared chunks under `server/chunks/` ride the entry's `ChunkListUpdate` as module deltas, so their content-hash paths no longer matter. - The Node hmr client applies `ChunkListUpdate` by feeding each nested merged update through the shared apply path. Modules that appear in an "added" chunk but already exist in the module cache were moved by a chunk rename and are treated as modified. - On a successful partial apply, the hot reloader clears the manifest cache for updated chunks and notifies browsers to refetch RSC, without clearing `require.cache`. ### Test Plan Adds a series of e2e tests to catch the described manual test plan in both entry and dynamic chunks. |
||
|
|
8cfa551041 |
Turbopack: extract chunk list version/update into turbopack-ecmascript (#95661)
This moves modules out of `turbopack-browser` and into `turbopack-ecmascript` so it can be used in the server hmr implementation as well. Move the chunk-list version, update, and merged-update wire types out of `turbopack-browser` into a shared `turbopack-ecmascript::chunk_list` module so both the browser and node chunking contexts can build on them. Rename `EcmascriptDevChunkListVersion` to `ChunkListVersion` and make `update_chunk_list` runtime-agnostic (plain path->`VersionedContent` maps). Pure refactor: no behavior change. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
88155cd421 |
Turbopack: aggregate server HMR into one subscription (#94948)
This replaces per-chunk Server HMR turbo tasks with a single firehose
subscription that diffs every HMR chunk. This significantly cuts the
number of tokio task churn on projects with many server chunks and
centralizes the diff/clear logic. It leads to a multi-second saving in
both cold and warm builds in a large app.
This PR also renames `clear()` to `reEvaluateAllModulesExpensive()` to
label directly that this is a costly operation and should only be done
in exceptional circumstances.
A following PR will bring this to client chunks.
Rust:
- New `aggregate_hmr` module: `AggregateHmrVersion` keyed by chunk path,
`merged_partial_update` builder, and `is_hmr_eligible_chunk` (excludes
`.map` files, which would force every diff to `Total`).
- `Project::all_hmr_version_state` / `all_hmr_update` aggregate over the
whole `hmr_root_path`. The seed transition emits an empty `Partial` so
the JS consumer doesn't treat it as a restart and wipe handlers the
triggering request just populated. Any chunk requiring `Total`/`Missing`
escalates the batch.
- `VersionedContentMap::hmr_chunks_in_path` lists eligible chunks with
their `VersionedContent`.
NAPI:
- `projectAllHmrEvents(target)` returns a single subscription.
JS:
- `setupServerHmr` subscribes once via `allHmrEvents` instead of fanning
out over `hmrChunkNamesSubscribe`.
- `reEvaluateAllModulesExpensive()` evicts every chunk under
`server/chunks/` from `require.cache` directly rather than tracking
subscriptions. This is a bit fragile as it relies on the path prefix and
scanning require.cache.
|
||
|
|
c23d1a946c |
feat(turbopack): support import.meta.env (#96225)
## Summary - add native Turbopack support for `import.meta.env.DEV`, `PROD`, `MODE`, `BASE_URL`, and `SSR` - statically analyze built-in values for dead-code elimination while exposing the complete runtime object - derive `BASE_URL` from the Next.js `basePath` and include Vite-compatible trailing-slash formatting - add TypeScript declarations, focused execution and Next.js integration coverage, and Vite migration documentation ## Verification - `cargo fmt --all -- --check` - `cargo nextest run -p turbopack-tests -E 'test(import_meta_env)'` - `pnpm build-all` - `pnpm --filter=next types` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/e2e/import-meta-env/import-meta-env.test.ts` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/e2e/import-meta-env/import-meta-env.test.ts` <!-- NEXT_JS_LLM --> |
||
|
|
9e59ff0a42 |
Update vendored @mswjs/interceptors to 0.41.9 (#96059)
The previous `@mswjs/interceptors` has a bug where the response it forwarded never set the [`complete` field](https://nodejs.org/api/http.html#messagecomplete). This wasn't an issue so far (or not reported) but is an issue for `httpxy` which relies on an accurate `complete` field. Bumping `@mswjs/interceptor` now so that we can switch from `http-proxy` to `httpxy`. |
||
|
|
3f2cf7e1a0 |
Turbopack: support import.meta.glob caseSensitive option (#96226)
## Summary Add Vite-compatible `caseSensitive` support to `import.meta.glob()`. Matching remains case-sensitive by default, while `caseSensitive: false` applies case-insensitive matching to directory traversal, filenames, positive patterns, and negative patterns. The option is included in matcher equality and non-default virtual module identity, and is exposed through diagnostics, TypeScript declarations, and the Turbopack options reference. ## Verification - `cargo test -p turbo-tasks-fs glob` - `cargo nextest run -p turbopack-tests -E "test(import_meta_glob) and not test(import_meta_glob_errors)"` - `cargo fmt --all -- --check` - `pnpm build-all` and `pnpm --filter=next types` are blocked by current-canary `sharp.default` type errors in `packages/next/src/server/image-optimizer.ts` <!-- NEXT_JS_LLM --> |
||
|
|
3214020b2d |
Turbopack: Fix missing canonicalization of paths and always use verbatim paths internally for Windows (#95668)
I was seeing failures on https://github.com/vercel/next.js/pull/95628 and went down this rabbit hole. - We were (incorrectly) not canonicalizing the `DiskFileSystem` root dir. This happened to work out for us because pnpm also wasn't canonicalizing junction point targets, and so the root dir prefix matched. However after `#95628`, pnpm started canonicalizing junction point targets because we added `pnpm-workspace.yaml` files. This was just coincidence and indicative of much deeper problems. - `dunce` isn't a good fit for us. It picks a win32 or verbatim representation based on path length, which would mean that long paths inside of a short root path would never work because we'd never be able to strip the prefix, since we'd be looking for a win32 path prefix. Omnipath is better for what we want to do: https://docs.rs/omnipath/latest/omnipath/windows/trait.WinPathExt.html - The windows verbatim path format works with long paths (>260 characters) and behaves a lot closer to cross-platform unix paths, which is what we want. E.g. if somebody tries to import a JS module from a Windows 8.3 short path, it should fail. This will fail with verbatim paths (good!). This PR *always* uses the verbatim path format internally within `DiskFileSystem`. - `read_link` was re-inventing `try_from_sys_path` but badly. Just call `try_from_sys_path`. - `turbopack/crates/turbo-tasks-fs/src/embed/file.rs` was dead code. - `validate_path_length_inner` was incorrectly referring to verbatim paths as UNC paths. These are two different things. - Canonicalize any absolute symlink targets outside of the `DiskFileSystem` root path, resolving any symlinks they may have, normalizing case-insensitive paths, and handling Windows 8.3 short name format. ## Some Context About Windows Path Formats: - win32 paths: These are your normal user-friendly `C:\foo\blah` paths. When calling most filesystem APIs, these are limited to 260 characters unless the user has enabled long paths on the system, and avoiding the limit with these paths also requires a [`longPathAware` application manifest](https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#longPathAware). You can mix `/` and `\` in win32 paths, and Windows will normalize the path separator for you. - verbatim paths: This is a rust-specific name (used by the stdlib) for the "extended length" prefixed paths. These are paths starting with `\\?\`. These paths can be up to ~32,767 characters long. They do not support Windows 8.3 shortnames. They do not normalize between `/` and `\`, and do not support `.` or `..` components, however [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join) will do this normalization if given a verbatim path. - Windows 8.3 shortnames: A legacy format where paths can be represented by 8 characters, followed by a tilde and a digit (or hash in some extreme cases where there are many collisions), followed by a 3 character file extension. NTFS supports this, [ReFS](https://learn.microsoft.com/en-us/windows-server/storage/refs/refs-overview) (used by [Windows Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/)) does not. - UNC ("Universal Naming Convention"): This is a specific representation of paths used for remote files on network servers. - `file://` URIs: These must be formed from [win32 style paths](https://docs.rs/url/latest/url/struct.Url.html#method.from_file_path), though the normal 260 character limit typically does not apply here. - canonicalize: Corresponds to [`std::fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html). This involves actual fs operations (that we can't easily track), and recursively resolves symlinks. It will fail if the file path does not exist. On windows, this always returns a verbatim path. - wide format: Windows paths are utf-16. Generally we don't support paths that can't be encoded to utf-8, but we should try to fail as obviously as we can in these cases. We don't have good filesystem issue handling yet though. - case sensitivity: Generally paths are not case-sensitive on Windows or macOS, but are case-sensitive on most Linux filesystems. The exact definition of how case sensitivity works with unicode paths is highly platform and filesystem dependent. - junction points: The windows equivalent of symlinks (symlinks are also supported, but require developer mode to be turned on). These can contain an arbitrary path reference in any format (win32, verbatim, etc) with any case and with windows 8.3 short names. Junction points are always target absolute paths to directories. |
||
|
|
3ed60553da |
Revert "[turbopack] Don't SSR on pages only navigated to through a soft nav (#95539)" (#96028)
Causes issues with `use client` it seems. |
||
|
|
6bf4df1450 |
Fix Turbopack middleware matcher with i18n single locale (#96014)
https://github.com/vercel/next.js/security/advisories/GHSA-6gpp-xcg3-4w24 Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
e90189b940 |
[turbopack] Rename turbopackTreeShaking to turbopackModuleFragments (#95978)
This has been causing a lot of confusion when contributors have been debugging tree shaking, eg. https://github.com/vercel/next.js/issues/95698 and the linked issues. Turbopack's standard tree-shaking features are controlled through the `turbopackRemoveUnusedExports` and `turbopackRemoveUnusedImports` options which default to true. |
||
|
|
42ccb2eede |
[turbopack] Clean up server_chunking_context (#95908)
https://github.com/vercel/next.js/pull/95539/changes#r3598998062 |