App router handlers are already built with the Turbopack runtime, so
they can use server HMR. **We also already get subscription events for
their chunks.** However, unlike app pages which use
`__next_app__.require()` for dynamic devModuleCache lookup, route
handlers capture userland exports statically in AppRouteRouteModule at
construction time. This change makes routes behave a lot like pages,
dynamically loading the user's code when requests are made. This way, we
can freely invalidate and reload it when changes are made without
evicting the entire require cache.
Only `next dev` uses this lazy evaluation, as `next start` continues to
eagerly import route handlers.
This change removes the `isAppPage` restriction from `usesServerHmr`,
extending server HMR coverage to all App Router entries (pages and route
handlers) built with the Node.js runtime.
For route handlers, we also clear the entry chunk from Node.js
`require.cache` on each rebuild so the next `requirePage()` call
re-executes the entry and obtains fresh module exports from
`devModuleCache` (which HMR updates in-place).
Test Plan: Added an additional e2e test, `with-dep`
## Summary
- Add `'turbopack-transition': 'next-server-utility'` to all server-side imports in the `app-page.ts` template so they are properly placed in the server utility layer, rather than relying on `SharedMerged` chunk groups to handle them
- Remove the now-unnecessary `next-server-utility` transition from `fillMetadataSegment` import in `app_page_loader_tree.rs`
- Add `SharedMultiple` chunk group variant to support multiple shared entries without a merge tag
- This avoid passing the `parent` into `SharedMerged`, which did break the layout segment optimization.
- Refactor `app_module_graphs` to combine `client_shared_entries` + `has_layout_segments` into a single `Option` parameter, simplifying the API and removing the dead code path for non-page endpoints
## Test plan
- [ ] Verify dev and production builds work with app pages that have layout segments
- [ ] Verify route handlers still build correctly (no client runtime entries passed)
- [ ] Run existing app-dir e2e tests
### What?
Remove the `TURBOPACK_STATS` feature that generated webpack-compatible
`webpack-stats.json` files from Turbopack's output.
**Deleted:**
- `crates/next-api/src/webpack_stats.rs` — the entire 305-line Rust
implementation that built webpack-format stats (assets, chunks, modules,
entrypoints) from Turbopack's module graph
**Rust changes:**
- `crates/next-api/src/lib.rs` — removed `mod webpack_stats;`
- `crates/next-api/src/project.rs` — removed
`should_create_webpack_stats()` which read the `TURBOPACK_STATS` env var
- `crates/next-api/src/pages.rs` — removed `generate_webpack_stats`
import and stats output emission
- `crates/next-api/src/app.rs` — same
**TypeScript changes:**
- `packages/next/src/shared/lib/constants.ts` — removed `WEBPACK_STATS =
'webpack-stats.json'` constant
- `packages/next/src/shared/lib/turbopack/manifest-loader.ts` — removed
`loadWebpackStats()`, `writeWebpackStats()`, `mergeWebpackStats()`, the
`webpackStats` field, webpack type imports, and the `TURBOPACK_STATS`
env guard in `writeManifests()`
- `packages/next/src/server/dev/turbopack-utils.ts` — removed
`shouldCreateWebpackStats` flag and two `loadWebpackStats()` call sites
- `packages/next/src/build/handle-entrypoints.ts` — same
### Why?
This feature is no longer needed and adds dead complexity. It was opt-in
via `TURBOPACK_STATS=1` but nothing in the current codebase or tooling
depends on it.
### How?
Pure deletion — no behavior change for any existing user, as the feature
was opt-in via an environment variable that is no longer checked.
**Intentionally left untouched:**
- Webpack's own `stats.json` generation (non-Turbopack path)
- The Turbopack bundle analyzer / `analyze` feature
- `route-bundle-stats.json` in `.next/diagnostics/`
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
This enables server HMR (formally, this is called server fast refresh)
by default for app router pages. For now, it's still disabled for routes
and middleware even with app router.
- Replaces `--experimental-server-fast-refresh` with
`--no-server-fast-refresh` to opt out
- Updates telemetry to record `feature.serverFastRefreshDisabled` when
the disable flag is passed
- [x] Verify `next dev` enables server HMR by default (Turbopack, app
dir, non-edge routes)
- [x] Verify `next dev --no-server-fast-refresh` disables it
- CI
```
// This is only used by Webpack to correctly output the manifest. It's value shouldn't be relied
// upon externally. It's possible that the same action can be in different layers in a single
// page, which cannot be modelled with this API anyway.
```
Followup to https://github.com/vercel/next.js/pull/90978, which fixed the `ChunkGroup`s passed to `.chunk_group()`, forgot to update the the module graph entry types, which didn't match up.
The `ChunkGroupEntry::Shared(ResolvedVc::upcast(*server_component))` one was more involved:
- We used to have `layout.js [app-rsc] (Next.js Server Component)` --ChunkingType::Shared--> `layout.js [app-rsc]` (the actual module)
- But this meant that the actual chunk group was `Shared(server_component.await?.module)` (the inner one)
- This wasn't possible to use for the module graph construction though, as the client reference transform needs to see the `layout.js [app-rsc] (Next.js Server Component)` module during the traversal, to infer the parent server component of the given subgraph.
So instead, move to
- `loader-tree in the template`
- --ChunkingType::Shared-->
- `layout.js [app-rsc] (Next.js Server Component)`
- --ChunkingType::Parallel-->
- `layout.js [app-rsc]`
The reason for that `ChunkingType::Shared` is so that we can chunk arbitrary modules that are loaded by `ChunkGroup::Entry`. Meaning that we can do `chunk_group(Shared(root_layout))).concat(chunk_group(Shared(layout))).concat(chunk_group(Entry(page)))` and rely on turbotask caching to deduplicate layout chunking across pages.
1. This is misldeading in the bundle analyzer view
2. If you have a Nextjs app consisting of only API routes, then there is
no need to ever write out any polyfill chunk.
### What?
Refactors the `ModuleReference` trait to make `chunking_type()` and `binding_usage()` methods return direct values instead of `Vc<T>` wrapped values, removing the need for async task functions.
Also removes the `get_referenced_asset` task from `EsmAssetReference`, inlining its logic into the callers.
### Why?
This change simplifies the API by eliminating unnecessary async overhead for methods that typically return simple, computed values. The previous implementation required `#[turbo_tasks::function]` annotations and `Vc<T>` wrappers even when the methods didn't need to perform async operations or benefit from caching.
### Impact
| Metric | Base | Change | Delta |
|--------|------|--------|-------|
| Hits | 35,678,143 | 35,845,124 | **+166,981** |
| Misses | 9,418,378 | 7,910,986 | **-1,507,392** |
| Total | 45,096,521 | 43,756,110 | **-1,340,411** |
| Task types | 1,306 | 1,277 | **-29** |
29 task types were removed, eliminating **2.6M total task invocations** (1.1M hits + 1.5M misses):
- **`chunking_type`** — 21 task types removed across all `ModuleReference` implementors (~952k invocations)
- **`binding_usage`** — 6 task types removed (~527k invocations)
- **`BindingUsage::all`** — helper task removed (~36k invocations)
- **`EsmAssetReference::get_referenced_asset`** — removed and inlined (~1.08M invocations: 628k hits + 451k misses)
The removed `get_referenced_asset` hits reappear as +628k hits on `EsmAssetReference::resolve_reference` and `ReferencedAsset::from_resolve_result` (with zero increase in misses), confirming the work is now served from cache through the existing callers.
No tasks had increased misses — the removal is clean with no cache invalidation spillover.
I also ran some builds to measure latency
```
# This branch
$ hyperfine -p 'rm -rf .next' -w 2 -r 10 'pnpm next build --turbopack --experimental-build-mode=compile'
Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
Time (mean ± σ): 52.752 s ± 0.658 s [User: 376.575 s, System: 106.375 s]
Range (min … max): 51.913 s … 54.161 s 10 runs
# on canary
$ hyperfine -p 'rm -rf .next' -w 2 -r 10 'pnpm next build --turbopack --experimental-build-mode=compile'
Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
Time (mean ± σ): 54.675 s ± 1.394 s [User: 389.273 s, System: 114.642 s]
Range (min … max): 53.434 s … 58.189 s 10 runs
```
so a solid win of almost 2 seconds
MaxRSS also went from 16,474,324,992 bytes to 16,359,309,312 bytes (from one measurement) so a savings of ~100M of max heap size.
### How?
- Changed `chunking_type()` method signature from `Vc<ChunkingTypeOption>` to `Option<ChunkingType>`
- Changed `binding_usage()` method signature from `Vc<BindingUsage>` to `BindingUsage`
- Removed `ChunkingTypeOption` type alias as it's no longer needed
- Updated all implementations across the codebase to return direct values instead of wrapped ones
- Removed `#[turbo_tasks::function]` annotations from these methods
- Updated call sites to use `into_trait_ref().await?` pattern when accessing these methods from `Vc<dyn ModuleReference>`
- Removed `EsmAssetReference::get_referenced_asset`, inlining its logic into callers
- Added validation for `turbopack-chunking-type` annotation values in import analysis
- Fixed cache effectiveness analysis script
### What?
- Add canonical edge entrypoint metadata to edge function definitions in
the middleware manifest.
- Surface that canonical entrypoint through `build-complete` as
`edgeRuntime` metadata in adapter outputs.
- Update middleware and adapter tests to assert the new metadata instead
of relying on bundler-specific emitted file names.
### Why?
Adapter consumers need a stable way to load and invoke edge outputs
without depending on `next-server`'s sandbox handling or guessing which
emitted file is the executable entrypoint. The existing checks were also
brittle across webpack and Turbopack because the emitted file lists can
differ.
### How?
- Add an optional `entrypoint` field to `EdgeFunctionDefinition` and
populate it in both the webpack middleware manifest generator and the
Turbopack manifest emitters for app, pages, and middleware edge
functions.
- In `packages/next/src/build/adapter/build-complete.ts`, use that
canonical entrypoint for `filePath` and expose `edgeRuntime.modulePath`,
`edgeRuntime.entryKey`, and `edgeRuntime.handlerExport` for edge
outputs.
- Adjust the middleware manifest tests to validate cross-bundler
invariants and add adapter-config coverage for the new `edgeRuntime`
fields.
This is basically one-shot from Opus 4.6, with a minor tweak to the doc comment by hand.
Discussed this with @lukesandberg, and we realized that there's only one implementation of this trait, so it doesn't make a ton of sense as a trait.
### What?
This PR wires custom `cacheHandler` / `cacheHandlers` through the
remaining edge entrypoints so edge app pages, edge app routes, edge
pages SSR, middleware, and edge API routes can all see the configured
handlers.
It also adds an e2e regression suite covering:
- pages-router ISR revalidation with a custom incremental cache handler
- app-router cache handlers with cache components enabled
- edge app page + edge app route wiring when cache components are
disabled
### Why?
We already support custom cache handlers in non-edge paths, but several
edge code paths were not forwarding that configuration into the
generated entrypoints/runtime wrappers. That meant custom cache handlers
could be skipped in edge rendering and edge route execution, and we did
not have regression coverage around those cases.
### How?
- pass `cacheHandler` / `cacheHandlers` through the JS build entry
plumbing for edge server entries
- inject cache handler imports/registration into the webpack edge
templates and loaders
- mirror the same wiring in the Turbopack/Rust entry generation for edge
app pages, app routes, pages SSR, and middleware
- update the edge route wrapper to initialize and register cache
handlers before invoking the route module
- extend `next-taskless` template expansion with raw injection support
so the generated edge templates can add imports and registration code
- add `test/e2e/cache-handlers-upstream-wiring` fixtures to cover
pages/app, edge/non-edge, and revalidation behavior
This:
- Fetches `data/_app/analyze.data` alongside the current route's data
- Merges `_app` modules into the current route's `AnalyzeData` before
visualization using a `mergeAnalyzeData()`, which merges the two
datasets, reindexing the additional ones and recreating the binary data
format expected by the treemap.
## Test plan
- [x] Add a large dependency (e.g. `moment`) in `pages/_app.tsx`
- [x] Run `next experimental-analyze`
- [x] Open the bundle analyzer and select a pages page
- [x] Verify moment appears in the treemap
- [x] Verify App Router projects (no `_app`) work without errors
Previously, we used `RuleCondition::ReferenceType(ReferenceType)`. But there are some problem with using the type itself as a matcher:
1. the sub types' Undefined variant (like `CssReferenceSubType::Undefined`) always matched all possible variants. So it was actually impossible to match exactly `CssReferenceSubType::Undefined`
2. variants with more complex types were also impossible to match against, like `ReferenceType::Inner(ResolvedVc<...>)`
### What?
Implements dependency-level HMR accept and decline for Turbopack,
covering both ESM (`import.meta.turbopackHot`) and CJS (`module.hot`)
modules.
Previously Turbopack only supported self-accept (`module.hot.accept()`
with no arguments) and self-decline. This PR adds the full
dependency-targeted API:
- `module.hot.accept(dep, cb)` / `import.meta.turbopackHot.accept(dep,
cb)` — single dep
- `module.hot.accept([depA, depB], cb)` /
`import.meta.turbopackHot.accept([depA, depB], cb)` — array of deps
- `module.hot.decline(dep)` / `import.meta.turbopackHot.decline(dep)` —
single dep
- `module.hot.decline([depA, depB])` /
`import.meta.turbopackHot.decline([depA, depB])` — array of deps
### Why?
Libraries like `react-refresh` and user code often need to accept
updates for specific dependencies rather than self-accepting the entire
module. Without this, those HMR patterns fall back to full page reloads
in Turbopack.
### How?
**Runtime** (`turbopack-ecmascript-runtime`):
- Extended `HotState` with `acceptedDependencies`,
`acceptedErrorHandlers`, and `declinedDependencies` maps (keyed by
`ModuleId`)
- Updated `hot.accept()` and `hot.decline()` to handle string and array
signatures
- Updated `getAffectedModuleEffects` to check accepted/declined
dependencies when propagating updates through the module graph
- Added `'declined'` effect type that throws an `UpdateApplyError`
- Track `outdatedDependencies` (map of parent → set of updated deps)
alongside `outdatedModules`
- In the apply phase, invoke per-dependency accept callbacks with the
correct outdated deps
- Use `Set` for `outdatedDependencies` dedup (O(1) vs O(n) lookups)
**Compiler** (`turbopack-ecmascript`):
- Added `ModuleHotReferenceAssetReference` — a single asset reference
type for both accept and decline deps, with shared resolve logic for ESM
(`esm_resolve`) and CJS (`cjs_resolve`)
- Added `ModuleHotReferenceCodeGen` — generates code that replaces dep
string literals with resolved module IDs at compile time
- ESM binding auto-update: when an ESM module accepts a dependency that
it also `import`s, the compiler wraps the accept callback to re-import
the namespace variable (`__TURBOPACK__imported__module__<id> =
__turbopack_import__(<id>)`) before the user callback runs, so ESM
bindings reflect updated values without needing `require()`
- Added `import.meta.turbopackHot` as the ESM equivalent of
`module.hot`, with TypeScript type declarations in
`packages/next/types/global.d.ts`
- Static analysis extracts dep strings from
`module.hot.accept`/`decline` calls; non-analyzable deps emit a warning
with distinct error codes (`TP1204` for accept, `TP1205` for decline)
**HMR gating** (`CompileTimeInfo`):
- Added `hot_module_replacement_enabled` flag to `CompileTimeInfo` to
gate recognition of `module.hot` and `import.meta.turbopackHot` as
well-known objects
- Without this flag, production builds would recognize
`module.hot.accept(...)` and generate HMR-specific code, leading to
runtime errors
- Flag set to `true` for dev servers and `false` for production builds
across Next.js and turbopack-cli entry points
- `import.meta.turbopackHot` getter is only emitted when HMR is enabled
**Server HMR** (`next-api`, `next-core`):
- Server-side `compile_time_info` now also sets
`hot_module_replacement_enabled` so that `module.hot` /
`import.meta.turbopackHot` are recognized during analysis of server
modules
- Server HMR requires the `--experimental-server-fast-refresh` CLI flag;
the flag is passed through `ProjectOptions` to the Rust side so
`server_compile_time_info` only enables HMR when appropriate
**Tests** (`test/development/app-dir/hmr-dep-accept/`):
- ESM single dep accept — verifies parent module is not re-evaluated,
accept callback fires, ESM bindings auto-update
- ESM array dep accept — same as above with `accept(['./dep-a',
'./dep-b'], cb)`
- CJS `module.hot.accept` — pure CJS dep observer pattern with `.cjs`
files
- Single dep decline — verifies full page reload occurs
- Array dep decline — verifies full page reload with
`decline(['./dep-a', './dep-b'])`
I don't think we have any tests that the server utility chunking actually works.
But I can't actually properly remember why we added it in the first place: https://github.com/vercel/next.js/pull/70461
Now all of the chunking context functions accept `ChunkGroup`
The names are bad, but `entry_chunk_group` and `ChunkGroup::Entry` have nothing to do with each other:
- `entry_chunk_group` means: it's a standalone file where `node path/to/chunk.js` will execute it
- `ChunkGroup::Entry` = it's the top-level chunk group
For example a `evaluated_chunk_group(ChunkGroup::Entry)` is also fine
This method is needed with `turbo-tasks` to implement `OperationVc`, but exposing it to other crates turns it into a footgun, because it allows you to attempt to read a `Vc` or `ResolvedVc` with strong consistency, which doesn't have a well-defined meaning.
## Summary
A series of bug fixes, refactors, and code organization improvements to Turbopack's tree shaking and side effect optimization:
- **Fix `ModulePart` handling for `FreeVarReference::EcmaScriptModule`** — pass `ModulePart` correctly so free variable references resolve to the right module part
- **Extract `EcmascriptModuleRenameModule`** — split the rename/reexport logic (`RenamedExport`, `RenamedNamespace`) out of `EcmascriptModuleFacadeModule` into a dedicated `rename` module, shared between side effects optimization and tree shaking
- **Simplify `EcmascriptModuleFacadeModule`** — now only handles the `Facade` case (locals + reexports), no longer overloaded with rename variants
- **Reorganize tree shaking code** — moved `asset.rs` → `part/module.rs`, `chunk_item.rs` → `part/chunk_item.rs` + `side_effects/chunk_item.rs`, `side_effect_module.rs` → `side_effects/module.rs` for clearer separation between part modules and side effect modules
- **Use `EsmExport` in `FindExportFromReexportsResult`** — reduces code duplication by reusing the existing `EsmExport` enum
- **Minor simplifications** — code deduplication, reordered checks, and general cleanup
# What
Add turbofmt! and turbobail! proc macros for async string formatting
with Vc<T> and ResolvedVc<T> values.
A new, agent-friendly `FORMATTING.md` file is added to help agents
figure out how these macros work.
# Why
Formatting strings that include turbo-tasks values currently requires
manually .await?ing each Vc, then calling format!, then converting to
RcStr via .into(). By using macros, we can reduce a lot of boilerplate
and potentially offer some opportunities for optimizations by reducing
intermediate, throwaway tasks.
# How
We make use of the "auto-deref" pattern in Rust to choose the "best"
formatting option: ValueToString or Display.
- Zero-alloc FormatIter: A shared iterator parser over format strings
(RawString, EscapedBrace, VarRef, VarRefFormat) that both macros and the
derive use, replacing duplicated manual parsing.
- Borrow-based capture: turbofmt! uses async { .. } (not async move), so
non-Copy types like FileSystemPath can be used in multiple turbofmt!
calls without cloning.
- Debug on StringifyType: Enables {:?} format specs in turbofmt!.
- Converted ~30 call sites across next-core, turbo-tasks-fs,
turbopack-core, turbopack-ecmascript, and others.
# What's Left
- Constants cannot be formatted with turbofmt! yet because of arcane
Rust hygiene rules (similar issue to
https://github.com/rust-lang/rust/issues/131446)
- rust-analyzer doesn't show these format strings as "real" format
strings yet because of limitations of the analyzer, but this should be
possible to make work.
- Automatic derive for ValueToStringRef to avoid intermediate tasks in a
few places where formatting is trivial
- turbowrite!/turbowriteln! could clean up a bit more code
### What?
Adds a lint script (`scripts/check-unused-turbo-tasks.mjs`) that detects
unused `#[turbo_tasks::function]`, `#[turbo_tasks::value]`, and
`#[turbo_tasks::value_trait]` definitions across the Rust codebase, and
removes all currently unused items.
### Why?
Dead turbo-tasks definitions add compilation overhead (each generates
trait impls, registrations, etc.) and make the codebase harder to
navigate. A lint check prevents regressions.
### How?
**New script** (`scripts/check-unused-turbo-tasks.mjs`):
1. Discovers all `.rs` files in `turbopack/crates/` and `crates/`
(excluding `turbo-tasks-macros-tests`)
2. Parses three kinds of turbo-tasks annotations:
- `#[turbo_tasks::function]` → extracts `fn` name + context (free,
inherent impl, trait impl, trait default)
- `#[turbo_tasks::value]` → extracts `struct`/`enum` name
- `#[turbo_tasks::value_trait]` → extracts `trait` name
3. Scans all files for identifier references, excluding definition sites
4. Reports any name that has no external references
The script is added to the `pnpm lint` command (runs in parallel with
other checks).
**Removed 23 unused items** across 18 files:
| Kind | Count | Examples |
|------|-------|---------|
| `value` (struct/enum) | 9 | `OutputAssetsWithAvailability`,
`ModuleNameMap`, `OptionModuleIds`, `LockedVersions`,
`AssetContentSnapshot` |
| `function` | 14 | `client_env`, `next_js_file`, `convert_path`,
`is_hot_module_replacement_enabled`,
`any_content_changed_of_output_asset` |
Also cleaned up imports that became unused after the removals.
### What?
Skip the Pages Router global CSS restriction for `data:` URL imports
(similar to app router and mixed app/pages router)
### Why?
`data:text/css` should work the same no matter if pages router, app
router or mixed router usage
### How?
the validation in `crates/next-api/src/module_graph.rs` already had a
carve-out for `node_modules`. This adds the same for data URL modules.
fixes#89900
- [x] Related issues linked using `fixes #89900`
- [x] Tests added
---------
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
<!-- 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
### 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
## 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 #
-->
Follow-up to https://github.com/vercel/next.js/pull/86266
This was mostly LLM-generated, but it's just moving things around and threading through a config option.
We'd like to be able to turn this on/off with an experimental config option:
\- Fixes issues where RA thinks a bunch of code is dead.
\- Lets us isolate potential issues to the worker thread implementation.
\- Provides an escape hatch for users in case there are issues.
\- Better fits the cargo "additive feature" model.
This allows specifying a different deployment id for static immutable assets
- [x] rename `getImmutableAssetToken` to `getAssetToken` (because it can be non-immutable as well)
- [x] disable for webpack
Closes PACK-6534
For https://github.com/nextjs/adapter-vercel/pull/25
Part of PACK-6802
1. refactor `turbopack-node` to run webpack loaders via nodejs
`worker_threads`
2. the `child_proccess` loader runner is keep in the crate feature to
run unit tests in `turbopack-tests` or run `turbopack-cli`
3. if wanting to switch back to `child_proccess` loader runner, change
features of `turbopack_node/worker_pool` to
`turbopack_node/process_pool` in crate `next-swc-napi`
4. after switched to `worker_threads` loader runner, debugging with
`CDP` (spawn child_process via `--inspect-brk`) doesn't work anymore,
but you can use breakpoint debugging in IDE (like vscode) directly
---------
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
We've had some really bad bugs in the past, which can be hard to root-cause due to eventually-consistent reads a the top-level, e.g. https://github.com/vercel/next.js/pull/77511
Outside of tests, there's no good justification for doing an eventually-consistent read at the top level. It may leak incomplete computations, which can contain errors that would otherwise be resolved by a strongly consistent read.
The basic idea here is simple:
- Store a flag in `CURRENT_TASK_STATE` indicating that we're in a top-level task.
- When reading a task output or cell, check for that flag.
But, there are a few complications:
- `RawVc`'s read and resolution logic is wrong, and internally uses multiple eventually consistent operations after a strongly consistent read.
- For this to be correct, it needs to happen as one atomic operation inside of the task backend, but it would change the backend API contract, so it's not straightforward to do that.
- I added a `SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK` flag that I'm using for now.
- There are a ton of places that are doing eventually consistent reads for no good reason. These aren't causing problems today, but we should fix them instead of suppressing them. Claude helped with this, but it's still a lot.
- Some tests actually do need eventually-consistent reads because they're testing some internal behavior of turbo-tasks. I added an intentionally long-named `unmark_top_level_task_may_leak_eventually_consistent_state` helper for this.
I manually tested `turbopack-cli` against our bundler-benchmark repository that uses it.
## Summary
Adds the Next.js version as a `version` field on the Turbopack
"initialize project" tracing span. The version is passed from the
TypeScript side (`process.env.__NEXT_VERSION`) through the NAPI bindings
into Rust `ProjectOptions`, where it is recorded on the span.
This makes it easy to correlate trace data with the specific Next.js
version that produced it.
Also standardizes all version reads in `packages/next/src/` to use
`process.env.__NEXT_VERSION` (which is inlined at build time by
`taskfile-swc.js`) instead of `require('next/package.json').version` or
`import { version } from 'next/package.json'`.
### Changes
**Trace span (commit 1):**
- **`crates/next-api/src/project.rs`** — Added `next_version` field to
`ProjectOptions`; recorded as `version` on the `"initialize project"`
span
- **`crates/next-napi-bindings/src/next_api/project.rs`** — Added
`next_version` to `NapiProjectOptions` and wired it through the `From`
impl
- **`crates/next-build-test/src/main.rs`** — Added `next_version` to
test `ProjectOptions` init
- **`packages/next/src/build/swc/generated-native.d.ts`** — Added
`nextVersion` to the TypeScript `NapiProjectOptions` interface
- **`packages/next/src/server/dev/hot-reloader-turbopack.ts`**,
**`packages/next/src/build/turbopack-build/impl.ts`**,
**`packages/next/src/build/turbopack-analyze/index.ts`** — Pass
`nextVersion: process.env.__NEXT_VERSION` when creating the Turbopack
project
- **`test/development/basic/next-rs-api.test.ts`** — Added `nextVersion`
to both `createProject` call sites
**Consistent `__NEXT_VERSION` usage (commit 2):**
- **`packages/next/src/server/dev/hot-reloader-shared-utils.ts`** —
`require('next/package.json').version` → `process.env.__NEXT_VERSION`
- **`packages/next/src/lib/patch-incorrect-lockfile.ts`** —
`nextPkgJson.version` → `process.env.__NEXT_VERSION`; narrowed import to
only `optionalDependencies`
- **`packages/next/src/telemetry/events/swc-load-failure.ts`** — `import
{ version as nextVersion }` → `process.env.__NEXT_VERSION`; narrowed
import to only `optionalDependencies`
## Test Plan
- Verified Rust compilation with `cargo check -p next-api`, `cargo check
-p next-build-test`
- Verified TypeScript types with `pnpm --filter=next types`
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Replaces the runtime `mark_root()` call with a declarative `root` flag
on the `#[turbo_tasks::function]` attribute macro. This moves root task
aggregation number assignment from an imperative runtime call to a
compile-time declaration, making the intent clearer and applying the
aggregation number at task creation time rather than during execution.
### What changed
- **Macro parsing**: Added `root` as a new flag option for
`#[turbo_tasks::function(root)]` (can be combined with other flags, e.g.
`#[turbo_tasks::function(operation, root)]`)
- **NativeFunction**: Added `is_root: bool` field and parameter to all
three constructors (`new_function`, `new_method`,
`new_method_without_this`)
- **Backend**: In `get_or_create_persistent_task` and
`get_or_create_transient_task`, checks `native_fn.is_root` on brand-new
task creation (not cache hits or backing storage restores) and sets
`UpdateAggregationNumber` with `u32::MAX`
- **Usage site**: Changed `whole_app_module_graph_operation` in
`crates/next-api/src/project.rs` from
`#[turbo_tasks::function(operation)]` + `mark_root()` to
`#[turbo_tasks::function(operation, root)]`
- **Cleanup**: Removed `mark_root()`, `set_own_task_aggregation_number`
from `TurboTasksApi` trait, `Backend` trait, and all implementations
(turbo-tasks-backend, turbo-tasks-testing)
### Key design decisions
- Root aggregation number is only set on **brand-new task creation**
(Vacant entry in the task cache when not found in backing storage
either), not on cache hits or when restoring from backing storage
- The `root` flag is stored on `NativeFunction` (which is a static
singleton per function), so it's accessible via
`CachedTaskType.native_fn.is_root` in the backend without any additional
runtime overhead
## Test Plan
- `cargo check -p turbo-tasks-macros -p turbo-tasks -p
turbo-tasks-backend -p next-api` passes
- `cargo test -p turbo-tasks-backend` passes
- `pnpm swc-build-native` builds successfully
We want `FileContent.content_hash()` to only hash the content bytes. This is also needed to produce the correct hashes [for SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity)
However, the default `DeterministicHash` and `Hash` impls are ill-suited for that because they're a generic hashing infrastructure.
Hashing only the content bytes isn't what you want actually, otherwise `hash(("a", "b", "c"))` would be the same as `hash("abc")`
https://doc.rust-lang.org/std/hash/trait.Hasher.html#method.write_str describes this problem as well
This is exactly what we want for Rope though, so this adds a `Rope.content_hash() -> impl DeterministicHash`.
Additionally, this changes `content_hash` to accept a HashAlgorithm to prepare for sha SRI hashes. This required changing the return type to `RcStr`, because of the variable hash lengths. But the return value was always `encode_hex`d everywhere anyway.
## Summary
Reworks Turbopack deferred entries in production build so deferred
routes are processed last in the same project lifecycle, with
`onBeforeDeferredEntries` executed between the non-deferred and deferred
phases.
## What changed
- Moved deferred build phasing into Rust in
`project_write_all_entrypoints_to_disk`:
- non-deferred phase
- `onBeforeDeferredEntries`
- deferred phase
- Kept callback invocation on the Rust side via the existing node-worker
callback plumbing.
- Narrowed invalidation after `onBeforeDeferredEntries` to deferred app
source subtrees instead of invalidating the whole project filesystem
cache.
- Added `DiskFileSystem::invalidate_path_and_children_with_reason(...)`
in `turbo-tasks-fs`.
- Derived deferred invalidation dirs from deferred routes’
`original_name` values.
- Retained a full-invalidation fallback only when app-dir path
resolution is unavailable.
- Deferred route selection still includes both app pages and app route
handlers.
## Validation
- `cargo check -p next-napi-bindings -p turbo-tasks-fs`
- `pnpm test-start-turbo
test/e2e/deferred-entries/deferred-entries.test.ts` (21/21 passing)
- Validated against [workflows
repo](https://github.com/vercel/workflow/pull/1058) and v0
Confirmed the fix locally on the original app I faced the issue with.
### Why?
When using `--debug-build-paths`, a pattern with a route group on
Turbopack, it fails with an error:
```
Error [PageNotFoundError]: Cannot find module for page: /nested
at ignore-listed frames {
code: 'ENOENT'
}
```
Current Behavior:
https://github.com/vercel/next.js/actions/runs/21544427008/job/62083680496?pr=89336#step:35:459
The bug lives in `crates/next-api/src/project.rs`, specifically in
`from_debug_build_paths()`. This function is responsible for deriving
route paths from debug build inputs, but it currently preserves route
groups when it shouldn’t.
With the current behavior, an input like `app/(group)/nested/page.tsx`
produces the output `/(group)/nested`. In other words, the route group
segment is kept in the generated path.
However, Turbopack route keys explicitly strip route groups. In
`next-core/src/next_app/mod.rs`, the conversion from `AppPage` to
`AppPath` removes route group segments. For the same input, Turbopack
expects the route key to be `/nested`, not `/(group)/nested`.
This mismatch causes a downstream failure. `from_debug_build_paths()`
produces `/(group)/nested`, while the Turbopack route key is `/nested`.
Since `should_include_app_route()` relies on an exact string match, the
comparison fails. As a result, the route is excluded, the module is
never compiled, and the error ultimately surfaces as a
`PageNotFoundError`.
### How?
Strip route group `(xxx)` and parallel route `@xxx` segments in
`from_debug_build_paths()` to match how Turbopack normalizes route keys.
---------
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
## What?
Moves the `turbopackIgnoreIssue` config from `experimental` to the stable `turbopack` config as `turbopack.ignoreIssue`.
## Why?
Reviewers on #89682 agreed the API is stable enough to graduate from experimental. Also addresses a docs wording suggestion.
## How?
- **TypeScript types**: Moved `ignoreIssue` field from `ExperimentalConfig` to `TurbopackOptions` in `config-shared.ts`
- **Zod schema**: Moved validation from `experimentalSchema` to `zTurbopackConfig` in `config-schema.ts`
- **Serialization**: Moved RegExp serialization logic into the turbopack block in `build/swc/index.ts`
- **Rust**: Moved `ignore_issue` field from `ExperimentalConfig` to `TurbopackConfig` in `next_config.rs`, updated the `turbopack_ignore_issue_rules()` method to read from `self.turbopack`
- **Tests**: Updated config in test to use `turbopack.ignoreIssue` instead of `experimental.turbopackIgnoreIssue`
- **Docs**: Removed `version: experimental`, updated all code examples, moved entry from experimental table to stable config list in turbopack.mdx, fixed "Good to know" wording to say "prefer using more specific `path` patterns" (since `path` is already required)
## Summary
- Pass `asset_suffix_path` as `Vc<Option<RcStr>>` instead of eagerly resolving it to `Option<RcStr>` in the chunking context option structs
- Add filesystem cache size growth assertions to e2e tests to detect unbounded cache growth regressions
## Why
Previously, `css_url_suffix` was eagerly awaited in `project.rs` before being passed into `ClientChunkingContextOptions`, `ServerChunkingContextOptions`, and `EdgeChunkingContextOptions`. This caused a new chunking context `Vc` to be created for every build, duplicating the entire build in cache and recompiling it.
By keeping it as a `Vc`, the chunking context identity is stable across builds, preventing unnecessary cache duplication.
## How
- Changed `css_url_suffix` field from `Option<RcStr>` to `Vc<Option<RcStr>>` in all three chunking context option structs
- Removed `.owned().await?.clone()` in `project.rs` (3 call sites), passing the `Vc` directly
- Added `.owned().await?` at the point of use in the 5 context builder functions
- Added cache size measurements to `filesystem-cache.test.ts`: normal changes limited to 10% growth, renames allow up to 50% due to dead cache entries
## Summary
Adds deployment ID suffix (`?dpl=<deployment_id>`) to asset URLs referenced in CSS `url()` declarations (images, fonts) for both Turbopack and Webpack builds. This enables proper cache busting for CSS-referenced assets when using `deploymentId` in `next.config.js`.
### Turbopack
- Added `static_suffix` field to the existing `UrlBehavior` struct, providing a build-time constant suffix for contexts that cannot use dynamic JS expressions (e.g., CSS `url()` references)
- CSS URL resolution now calls `url_behavior(None)` and uses `static_suffix` instead of a separate method
- Wired the suffix from `next.config.js` `deploymentId` through `asset_suffix_path()` into all three chunking contexts (client, server, edge) via `default_url_behavior` and `url_behavior_override`
### Webpack
- Passed `deploymentId` through the CSS loader config pipeline (`webpack-config.ts` → `config/index.ts` → `css/loaders/` → `postcss-url-parser.ts`)
- Appended `?dpl=<id>` to resolved asset URLs in the PostCSS URL parser plugin, placed before any hash fragment so it remains a server-visible query parameter
### Test
- Added production test (`test/production/css-url-deployment-id/`) covering global CSS, CSS modules, and SCSS modules with image and font `url()` references
- Verifies all asset URLs contain the `dpl` query parameter in both Turbopack and Webpack modes
<!-- 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
### 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
## 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 #
-->
## What?
Adds the `experimental.turbopackIgnoreIssue` configuration option, which allows developers to suppress specific Turbopack warnings and errors based on file path, issue title, and/or description patterns. Filtered issues are hidden from both CLI output and the error overlay.
## Why?
Developers need a way to suppress noisy or expected warnings from Turbopack without disabling all warnings. This is useful for:
- Suppressing warnings from third-party code, generated files, or optional dependencies
- Reducing noise in development server output
- Hiding expected issues from the error overlay
## How?
### Configuration → Filtering Pipeline
1. **Config types** (`packages/next/src/server/config-shared.ts`, `config-schema.ts`):
Each rule has a required `path` (glob string or RegExp) and optional `title`/`description` (exact string or RegExp). All specified fields must match (AND logic).
2. **RegExp serialization** (`packages/next/src/build/swc/index.ts`):
Converts RegExp objects to `{ source, flags }` and glob strings to `{ type: 'glob', value }` for transfer to Rust.
3. **Rust deserialization** (`crates/next-core/src/next_config.rs`):
`TurbopackIgnoreIssueRule`, `TurbopackIgnoreIssuePathPattern`, and `TurbopackIgnoreIssueTextPattern` types deserialize the config. A `turbopack_ignore_issue_rules()` turbo-tasks function converts them to `IgnoreIssue` rules with proper cache invalidation.
4. **Issue filtering** (`turbopack/crates/turbopack-core/src/issue/mod.rs`):
`IssueFilter` is a turbo-tasks value that holds both severity filters and ignore rules. `IgnoreIssuePattern` supports exact string, glob, and regex matching (using `turbo-esregex` for ES-style regex). Matching issues are excluded before severity filtering.
5. **Integration** (`crates/next-api/src/project.rs`, `route.rs`, `empty.rs`):
`Endpoint::project()` trait method provides access to the project config. `issue_filter()` builds the `IssueFilter` from project config. All issue collection points use the project's dynamic filter instead of the previous hardcoded `NEXT_ISSUE_FILTER`.
### Documentation
- New docs page: `docs/01-app/03-api-reference/05-config/01-next-config-js/turbopackIgnoreIssue.mdx`
- Updated Turbopack reference page with experimental options table: `docs/01-app/03-api-reference/08-turbopack.mdx`
### Testing
E2E test suite (`test/development/app-dir/turbopack-ignore-issue/`):
- **With config**: verifies matching warnings are suppressed from CLI output, matching errors are suppressed from the error overlay, and unrelated pages still compile normally
- **Without config**: verifies warnings and errors appear when no rules are defined
### Dependencies
- Added `turbo-esregex` for ES-style regex support in Rust
## Example
```js
// next.config.js
module.exports = {
experimental: {
turbopackIgnoreIssue: [
{ path: '**/vendor/**' },
{ path: 'app/**', title: 'Module not found' },
{ path: /generated\/.*\.ts/, description: /expected error/i },
],
},
}
```
This adds some basic JavaScript/TypeScript infrastructure for Next to receive and handle server-side HMR updates from Turbopack.
This:
- Renames some existing hmr code as explicitly client hmr
- Adds counterpart apis for server hmr (e.g. subscribe to server HMR events via `project.serverHmrEvents()`)
- Add `__turbopack_server_hmr_apply__` runtime function (this is a stub for now, just logs on updates)
**Alternatives considered**
A single firehose for hmr events, pushing the filtering to the JS side. I figured separate dedicated apis would be cleaner since they’d have to be filtered and switched on anyway.
**In future PRs**
- Rust: Make Turbopack send 'partial' updates for server file changes
- Node dev runtime: Implement module factory replacement in `__turbopack_server_hmr_apply__`
- add client notification to trigger re-fetch of rsc
- e2e tests
These `try_resolve_*` methods predate the introduction of the `ResolvedVc` type.
`ResolvedVc` has efficient synchronous `try_downcast`/`try_sidecast` functions, and there are only a few callsites that need to do this, so it makes more sense to split the resolution and the casting into to separate calls.