Commit Graph

1409 Commits

Author SHA1 Message Date
Niklas Mischkulnig 2d1d5268fb Upgrade to swc 70 (#94814)
<!-- 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

### 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 #

-->

---------

Co-authored-by: Will Binns-Smith <wbinnssmith@gmail.com>
2026-06-18 17:31:43 -07:00
Sam Poder 4b519bee13 [turbopack] Retry chunk loading on failure (#94918)
This is a trimmed down PR from
https://github.com/vercel/next.js/pull/88107, it achieves the same thing
(with much of the same code). However, it is Turbopack-only. This is
because the Webpack portions of that PR required patching React - we can
return to this in a different PR or I guess people could use a plugin?
(https://github.com/mattlewis92/webpack-retry-chunk-load-plugin)

---------

Co-authored-by: Jimmy Lai <laijimmy0@gmail.com>
2026-06-18 10:00:01 -07:00
Jude Gao 1cad9e989a Rename experimental.useExperimentalReact to blockingSSR (#94869)
`experimental.useExperimentalReact` names the React *build* it pulls in
rather than the feature it's there for. Rename it to `blockingSSR` — the
actual capability (React's blocking SSR, which emits `<link
rel="expect">` to hold first paint until the streamed shell is coherent)
— mirroring React's own feature flag so it's clear why the experimental
channel got enabled when grepping.

<!-- NEXT_JS_LLM_PR -->
2026-06-17 10:35:07 -04:00
Niklas Mischkulnig 8b24a83d29 Turbopack: don't run NFT for output:export or Edge (#94862)
- For `output: export`, there is no server (only SSG). So no need for
NFT
- For Edge, there are no files. So no need for NFT
2026-06-17 09:47:54 +02:00
Niklas Mischkulnig 9b8a7a1055 Turbopack: improve issue printing colors (#94858)
1. Align the code highlight marker color with the issue severity
2. Make the issue title colored
3. Prefix issues with `Warning` or `Error`


<img width="1383" height="862" alt="Bildschirmfoto 2026-06-16 um 18 59
03"
src="https://github.com/user-attachments/assets/f98c606d-4ea8-40c2-836d-e395f1df904c"
/>


<img width="1039" height="450" alt="Bildschirmfoto 2026-06-16 um 19 03
39"
src="https://github.com/user-attachments/assets/2747f914-0587-4178-a87d-344f24715c96"
/>



<details>
<summary>Old</summary>

<img width="1267" height="745" alt="Bildschirmfoto 2026-06-16 um 17 04
35"
src="https://github.com/user-attachments/assets/837739ff-82f7-4ea2-8767-ca7d866a8570"
/>

</details>
2026-06-16 21:29:37 +02:00
Jude Gao 71b70e01b8 Add experimental.useExperimentalReact to opt into React's experimental channel (#94861)
Opting into React's experimental channel — which emits `<link
rel="expect">` to hold first paint until the streamed shell is coherent,
avoiding flicker from partially-streamed HTML — currently requires
enabling an unrelated feature like `experimental.taint` as a side
effect, which is confusing. This adds
`experimental.useExperimentalReact` as a direct opt-in.

It feeds the existing `needsExperimentalReact` aggregation and the
matching Turbopack `react_channel` switch, selecting the
`react@experimental` build the same way `taint`, `transitionIndicator`,
and `gestureTransition` do. It's opt-in only: an explicit `false` can't
disable the channel when one of those still requires it (the taint APIs
only exist in the experimental build), so `assignDefaults` warns on that
contradiction. Covered by a webpack e2e test mirroring the existing
`taint` channel test.

<!-- NEXT_JS_LLM_PR -->
2026-06-16 15:08:47 -04:00
Niklas Mischkulnig fc05e6cbac Turbopack: improve NFT warning message (#94854)
<img width="1382" height="476" alt="Bildschirmfoto 2026-06-16 um 18 11
15"
src="https://github.com/user-attachments/assets/f0ac053a-92fc-4b72-aef7-18f02fd17a82"
/>
2026-06-16 19:59:28 +02:00
Will Binns-Smith 901c080394 rust react compiler: detect and build for react 18 (#94836) 2026-06-15 22:25:14 -07:00
Will Binns-Smith 77f02f8583 Turbopack: add experimental React compiler support (#94573)
- Adds experimental.rustReactCompiler config option to the next.js
config to opt in. It should be used alongside `config.reactCompiler` and
errors if not set.
- Only runs the compiler on client code (and SSR), not RSC
- When enabled, runs the compiler directly on Turbopack's swc AST with
no gen+reparse. This required passing through the original source text
into source transforms.
2026-06-15 15:31:02 -07:00
Luke Sandberg 5d69485828 [turbopack] Use final_read_hint in top level turbo tasks (#94712)
Use `final_read_hint` on top level turbo tasks

For many top level tasks where we are returning data there is no need for turbo-tasks to hold on to it afterwars, but these are typically also _transient_ and thus un-evictable.  So use the `final_read_hint` to allow turbo tasks to drop the cell after reading it.

The biggest impact here is HmrUpdateWithIssues that holds a potentially large hmr instruction.

From a simple benchmark loading 50 routes on a large site this drops the post eviction heap from 4.3G->2.5G
2026-06-12 11:22:42 -07:00
Jiwon Choi 74ce6b2e2f Reapply "Stabilize catchError and retry by removing unstable_ prefix" (#94623)
### What?

Reverts #94617, which had reverted #94610. This re-lands the
stabilization of the `catchError` API and the `retry` error prop by
removing their `unstable_` prefix across source, docs, and tests.

### Why?

#94610 was reverted in #94617 to unblock another change. This re-applies
it now that it is no longer blocked.

### How?

`git revert` of the revert commit. The repo's custom errors.json merge
driver handled the error registry: the original codes `1324`/`1325` had
been reclaimed by `instant` errors after the revert, so the reintroduced
messages were minted as new codes `1360` (`retry()` can only be used in
the App Router) and `1361` (`catchError` can only be used in Client
Components), keeping errors.json append-only.

The docs changelog adds `v16.3.0 | catchError became stable.` while
retaining the historical `v16.2.0 | unstable_catchError introduced.`
row.

### Verification

- `pnpm update-error-codes` (check_error_codes passes; errors.json in
sync)
- `pnpm build` (full JS build, exit 0)
- `pnpm --filter=next types` (exit 0)
- Not run: Rust/cargo build (`react_server_components.rs` change is a
one-line allow-list string revert; left to CI)

<!-- NEXT_JS_LLM_PR -->
2026-06-09 16:11:44 -07:00
Josh Story d424aa8ec4 Stabilize unstable_instant (#94578)
This API has experimental modes for build validation but the dev
validation is going to be stable in the next release.
2026-06-09 13:50:25 -07:00
Jiwon Choi 3d920355dd Revert "Stabilize catchError and retry by removing unstable_ prefix" (#94617)
Reverts vercel/next.js#94610

Unblock
2026-06-09 19:47:48 +00:00
Jiwon Choi 6b3936a332 Stabilize catchError and retry by removing unstable_ prefix (#94610)
### What?

Promotes two experimental error-handling APIs to stable by dropping the
`unstable_` prefix:

- `unstable_catchError` → `catchError` (exported from `next/error`)
- `unstable_retry` → `retry` (the prop Next.js injects into `error.js` /
`global-error.js` boundaries and `catchError` fallbacks)

### Why?

Both were introduced as `unstable_` in `v16.2.0` and are now stable as
of `v16.3.0`. The experimental prefix should be removed from the stable
surface.

### How?

- Renamed the `next/error` export (`error.d.ts`, `error.js`,
`src/api/error.ts`, `src/api/error.react-server.ts`) and the
implementation in `client/components/catch-error.tsx`.
- Renamed the framework-injected `retry` prop across
`error-boundary.tsx`, `builtin/global-error.tsx`, the dev-overlay error
boundary, and the `ErrorInfo` type.
- Updated the RSC client-only export list in the SWC transform
(`react_server_components.rs`) so importing `catchError` in a Server
Component still produces the "only available in Client Components"
error.
- Updated the thrown error messages and their `errors.json` entries
(codes 1138/1139), the TypeScript-plugin serialization-exemption rule,
all affected tests, the rspack test manifests, and the docs.
- Docs version-history tables keep the historical `unstable_` rows and
add a `v16.3.0` row noting the rename to stable.

This is a clean rename with **no** backward-compatible `unstable_`
alias.

### Verification

- Passed: pre-commit hooks (`prettier`, `eslint --fix`, `rustfmt`) on
all 35 changed files.
- Passed: repo-wide grep confirms no remaining `unstable_catchError` /
`unstable_retry` outside the intended historical version-history rows.
- In progress locally (covered by CI): `pnpm build-all` (native SWC
still compiling on a fresh worktree), then `pnpm --filter=next types`
and the targeted e2e suites (`app-dir/catch-error`, `app-dir/errors`,
`rsc-build-errors`, typescript-plugin `client-boundary`).

<!-- NEXT_JS_LLM_PR -->
2026-06-09 21:05:02 +02:00
Luke Sandberg a1c695a878 [turbopack] add an extension to trace files (#94587)
Add `.bin` extension to trace files

This will hint to tools like tailwind that scan for content not to open
these.
2026-06-09 17:08:03 +00:00
Luke Sandberg ea74495726 disallow reads from .next-profiles (#94570)
`next` already disallows reading out of `.next` during a build, this
extends that to the new `.next-profiles` directory

This is currently hardcoded (not end user configurable), so we just
hardcode the path in the next entrypoints.
2026-06-09 15:05:48 +00:00
Josh Story adb2913661 Stabilize export const prefetch (#94571)
This API is going to ship stable but some options may individually be
marked unstable
2026-06-08 18:30:55 -07:00
Sam Poder 409fa64d16 [turbopack] Use an arena for JSValues (#94297)
This PR takes the arena introduced in
https://github.com/vercel/next.js/pull/94296 and uses it! It's large but
shouldn't change functionality beyond the use of the arena. Some syntax
related to pattern-matching had to change because `bumpalo`'s `Box`
doesn't support the same pattern matching that the standard library's
`Box` does but the code should be equivalent.

`bumpalo`'s `Bump` type implements `Send` but not `Sync`, to allow us to
pass a `Bump` into async-methods we wrap it in `ThreadLocal` (see
https://docs.rs/thread_local/latest/thread_local/). `ThreadLocal`
implements `Sync` by creating a new bumpalo allocator for each thread.
We've tried to minimise the usage of `ThreadLocal` to just where it was
needed. The downside of `ThreadLocal` is that we may find ourselves
allocating memory for multiple arenas instead of just one. This is more
time allocating if there are multiple threads compared to one shared
allocation, however, it still appears to be a performance improvement
over individually allocating memory for each `JSValue`

---------

Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
2026-06-08 17:46:03 -07:00
Niklas Mischkulnig ff865573ba Turbopack: content hash polyfill sourcemap file (#94548)
The sourcemap file of the polyfill chunk wasn't actually content hashed.
it just used the name of the corresponding JS chunk.
It was still emitted into `_next/static/immutable`. So it wasn't
actually immutable.

Now, it's content hashed (and this also removed the ad-hoc sourcemap
construction from app.rs and instead uses the usual pattern that all
other chunks use to reference their corresponding sourcemap chunks).


We didn't catch this because it only happens with
`nextConfig.productionBrowserSourceMaps: true` which isn't the default.
The test now covers this as well
2026-06-08 12:00:31 +00:00
Niklas Mischkulnig f5718d2c44 Turbopack: refactor into get_next_client_transforms_rules (#94487)
Straight up moving that `.extend()` into the function that produced the
vector a few lines before.
2026-06-08 10:07:47 +02:00
Luke Sandberg 804b9a0bd4 [turbopack] Enable Effects to be evicted (#94173)
### What?
Enable Effects to be evicted.

Currently `EffectInstance` transitively holds a
`ReadRef<PersistentFileContent>` which means eviction cannot not free
the memory for the outputs. Because the `ReadRefs` are captured by the
task producing the `Effect` we cannot just drop them (otherwise we could
need to somehow re-execute the write to recover them). So instead we
need a new 'effect processing phase'

Here is our new state machine:
 * Phase 1: `emit` effects.
- At this point we capture the content as a `ResolvedVc` and the hash as
a value.
- In principle this means the hash and content can get out of sync, but
only within an eventual consistency race which callers should be
handling.
  * Phase 2: `take_effects`.
- This will call `capture()` on all the effects which will conditionally
read the content cell if the hash doesn't match the `EffectStateStorage`
  * Phase 3: `Effects::apply`.
- This will apply the effects for all effects whose hashes don't match
storage.
- At this point we may discover that a hash in storage changed between
`capture` and `apply` which will trigger a special `Retry` error and
`invalidate` `Phase 2`.

To facilitate the `Retry` logic a
`read_strongly_consistent_and_apply_effects` helper function handles it.
Which allows for a simple circuit breaker and some verbose
warnings/errors when we retry too many times.

Specifically:
- Split the `Effect` trait into `Effect` (emit-side, returns a
`Captured` payload) and `CapturedEffect` (apply-side). `
- Switch `Effects` to `cell = "new"`. Every producer re-execution
allocates a fresh cell value; the prior cell (and its captured payload)
drops on overwrite, cascade-releasing upstream
`ReadRef<PersistedFileContent>` strong counts. The captured payload also
drops naturally when the cell is evicted.
- Push the per-key state machine (`Unapplied` / `InProgress` / `Applied
{ value_hash, result }`, `EventGuard` panic recovery, `Event` listener
coordination) down into `EffectStateStorage::run_apply`.
`Effects::apply` becomes a thin aggregator over `dyn_apply()` calls.
- New `ApplyOutcome<E> { Failed(E), Retry }`. `CapturedEffect::apply`
returns this; `Retry` signals "capture elided content but storage
diverged before apply" — `Effects::apply` collects across the batch,
fires the producer's invalidator once, and surfaces
`EffectsError::Retry`.
- New `EffectStateStorage::matches_applied(key, hash)`.
`WriteEffect::capture` / `WriteLinkEffect::capture` consult this and
skip the `ReadRef<PersistedFileContent>` / `ReadRef<LinkContent>`
materialization when storage already holds `Applied { matching, Ok(())
}` — avoiding the disk-read + decompression hit on producer re-runs that
don't actually change output.
- 10 integration tests in
`turbopack/crates/turbo-tasks-backend/tests/effects.rs` cover the new
lifecycle: duplicate apply, sibling stomp re-apply, repeated apply with
unchanged state dedupe, `cell = "new"` producing distinct cells per
producer run, capture-skip on storage match, and the
capture-skip-then-stomp race that exercises the `Retry` path.

### Why?

A memory audit showed `EffectInstance` cells accumulating the majority
of the ram in an eviction session

#### Capture-time storage check (the perf story)

`Effect::capture` may consult `EffectStateStorage::matches_applied` and
elide the content materialization when storage already records the
target hash. `CapturedWriteEffect.content` becomes
`Option<ReadRef<PersistedFileContent>>` — `None` means "capture observed
`Applied { matching }`; no `body` to pass to `run_apply`". The
apply-side state machine then either dedup-hits (storage still matches →
cached `Ok`) or enters the no-body branch and returns
`ApplyOutcome::Retry`.

Reading shared mutable `EffectStateStorage` from inside a turbo-tasks
task is normally suspect (the state isn't a tracked input). It is sound
here because the apply-time re-check fires the producer's invalidator on
mismatch, turning the otherwise-untracked read into an
explicitly-tracked one via the `Retry` pathway. The producer reruns,
`capture` sees the new storage state, materializes content, and the next
`apply` succeeds.

#### `ApplyOutcome::Retry` and the invalidator

`Effects::apply` collects `Retry` signals across the parallel batch via
`AtomicBool`. `Failed(e)` errors fail-fast. After the batch, if any
`Retry` fired (and no `Failed`), the producer's invalidator runs once
and `EffectsError::Retry` propagates. Callers re-read the operation; the
producer reruns; fresh `capture` either materializes content (storage
diverged) or dedup-hits cleanly.

#### `turbo-tasks-fs`

`WriteEffect::capture` / `WriteLinkEffect::capture` call
`matches_applied` first and conditionally `.await?` the content
`ResolvedVc`. `CapturedWriteEffect::apply` /
`CapturedWriteLinkEffect::apply` dispatch through `run_apply(key, hash,
body)`, where `body` is `Some(closure)` iff content is `Some`.
`apply_inner` takes the unwrapped `&ReadRef<…>` as a parameter so the
option-unwrap is at the dispatch boundary (type-enforced).

### Risk: infinite retry loop

**Failure mode.** Two sibling producers A and B contend on the same key
with different hashes (e.g. two routes that both want to write
`chunks/foo.js` with different content). Each `Retry` from A's apply
invalidates A's producer; each `Retry` from B's apply invalidates B's
producer. In the worst case, A and B perpetually re-capture, race to
apply, stomp each other's storage state, and re-trigger Retry —
invalidating themselves on each cycle.

**Why we believe it terminates in practice.** A `Retry` only fires when
(a) capture observed `Applied { matching }` and elided content, and (b)
by apply time storage was stomped to a different hash. Each `Retry`
invalidates only its own producer (no cross-producer invalidation), so
without external input churn the system reaches a fixed point —
whichever producer applies last wins, and subsequent re-runs of the
loser's producer materialize content (because storage no longer matches)
and apply cleanly. The pathological case requires a real, ongoing race
fundamentally triggered by external invalidations (file edits).


<!-- NEXT_JS_LLM_PR -->
2026-06-07 22:33:00 +00:00
Luke Sandberg a51f3556c3 Remove turbopackMemoryLimit it is dead (#94483)
This feature has never worked, and we should just drop it
2026-06-07 22:01:38 +00:00
Luke Sandberg f32234cc1a Turbopack: Add an experimental option for eviction (#94439)
`experimental.turbopackMemoryEviction = "full" | false`

controls the new feature, currently it is disabled.
2026-06-07 16:38:35 +00:00
Luke Sandberg f4f3e19914 Turbopack: remove EcmascriptParsable::parse_original (#94465)
`EcmascriptParsable::parse_original` is used to access the original
module when using 'split modules', the only place that needed this was
already checking for split modules for other reasons so we can simply
remove this function from the trait and access the split module
'original' directly.

Also remove EcmascriptParseable::ty which was dead
2026-06-07 16:25:12 +00:00
Luke Sandberg c5f0da7bcb [turbopack] Remove turbotask functions from trait ResolveOrigin (#94324)
### What?

Removes the `#[turbo_tasks::function]` annotations from the
`ResolveOrigin` trait methods (`origin_path`, `asset_context`,
`resolve_options`), turning them into plain synchronous trait methods.
Callers obtain the value once via `Vc::into_trait_ref().await?` and then
call the methods directly on the resulting `TraitRef`.

This mirrors the earlier de-functionification of the `Issue` trait
(#92623).

### Why?

The `ResolveOrigin` implementations are all trivial — they return a
stored field or delegate a single call.

| Task (misses, before → after) | Misses removed |
| --- | --- |
| `ResolveOrigin::resolve_options` (dyn) | 92,608 → 0 |
| `EcmascriptModuleAsset::origin_path` | 64,779 → 0 |
| `ResolveOriginWithTransition::asset_context` | 52,292 → 0 |
| `ResolveOriginWithTransition::origin_path` | 52,292 → 0 |
| `EcmascriptModuleAsset::asset_context` | 41,164 → 0 |
| `PlainResolveOrigin::{origin_path, asset_context}` | 105 + 105 → 0 |
| `EcmascriptCssModule::{origin_path, asset_context}` | 32 + 32 → 0 |
| `CssModule::{origin_path, asset_context}` | 19 + 19 → 0 |

Net effect in that run: **~303k fewer cache misses** (total misses
6,197,068 → 5,893,545) — The ~1.35M eliminated cached-task *hits*
additionally remove the per-call task lookup/scheduling overhead.

The caching that genuinely matters is preserved one layer down —
`AssetContext::resolve_options` (which `ResolveOrigin::resolve_options`
delegated to) is still a cached task; its miss count is unchanged
(39,668) since those distinct results were always computed, and it
simply absorbs the call volume directly now (hit rate 57% → 92%).

## Build Performance: `ResolveOrigin` optimization

Benchmark across 10 runs per branch.

| Metric | Base | This branch | Δ |
|---|---|---|---|
| **Wall time** | 43.302 s | 42.692 s | **−0.61 s (−1.4%)** |
| **User time** | 314.249 s | 310.919 s | **−3.33 s (−1.1%)** |
| **Sys time** | 84.225 s | 84.618 s | +0.39 s (+0.5%) |
| **MaxRSS** | 14106.1 MB | 13883.4 MB | **−222.8 MB (−1.6%)** |

### Notes on significance

- **User time** is the cleanest signal: −1.1% with very tight variance
(base σ 0.4%, branch σ 0.3%). The drop (~3.3 s) is well outside the
noise — a real reduction in CPU work.
- **MaxRSS** is down a solid 1.6% (~223 MB) with tight variance on both
sides (σ ≈ 0.5–0.6%) — a clear, reproducible memory win.
- **Wall time** improves 1.4%, but variance is large (base σ 6.6%,
branch σ 4.5%, with one 50 s outlier in the base set), so treat this as
directional. It tracks the user-time improvement.
- **Sys time** is essentially flat (+0.5%, within its ~10% run-to-run
noise) — no meaningful change, as expected for a CPU/allocation
optimization.

### Invalidation tradeoff

The main behavioral risk is invalidation granularity. Removing the
per-method tasks removes their invalidation boundary: a consumer that
previously depended only on the `origin_path()` (or `asset_context()`)
task output now reads the value off the origin directly, so it is tied
to the origin cell rather than to that one method's cached output.

In practice this is narrow, because every `ResolveOrigin` implementor is
an immutable, content-addressed `#[turbo_tasks::value]` (only
`ResolvedVc`/owned fields) — a different field value means a different
cell, so "reading the whole value vs. one field" does not by itself
widen invalidation.

<!-- NEXT_JS_LLM_PR -->
2026-06-05 23:06:32 +00:00
Niklas Mischkulnig 74bb0d9a6d Turbopack: fixup cache handler tracing (#94477)
https://github.com/vercel/next.js/pull/94197 had a bug in the Turbopack
case, where it didn't properly trace all necessary files referenced from
`cacheHandler` and `cacheHandlers`.

If you imported `lodash` in the cache handler, then
- `node_modules/.pnpm/lodash@4.18.1/node_modules/lodash/lodash.js` was
correctly traced
but missing were
- `node_modules/.pnpm/lodash@4.18.1/node_modules/lodash/package.json`
and
- `node_modules/lodash` (the symlink)

The problem was that it wasn't including affecting sources of these
entrypoints, because it was treating those entrypoints as bundled:

```
SingleModuleGraph::new_inner(
        entries: Vec<ChunkGroupEntry>,
        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
        include_traced: bool,
        include_binding_usage: bool,
        entries_are_traced: bool, // <------ problematic
    ) -> Result<Vc<Self>>
```

should really have been 

```
SingleModuleGraph::new_inner(
        entries: &GraphEntries, // contains Vec<ChunkGroupEntry> and traced_modules: Vec<Modules
        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
        include_traced: bool,
        include_binding_usage: bool,
    ) -> Result<Vc<Self>> {
```
because the single `entries_are_traced: bool` can be different per
entry.
2026-06-05 20:50:32 +02:00
Niklas Mischkulnig 87ce7a7a70 Turbopack: refactor to use get_ecma_transform_rule (#94485)
Use a util function instead
2026-06-05 17:54:53 +00:00
Luke Sandberg a0dd232358 Simplify turbo-tasks-backend: collapse single-impl traits and remove redundant Arc layers (#93983)
### What?

Simplifies `turbo-tasks-backend` by removing abstraction layers that
each had a
single implementation, plus several redundant `Arc` indirections that
were no
longer load-bearing.

**Traits removed / collapsed (each had exactly one implementor):**

- **`KeyValueDatabase`** — folded into inherent methods on its only
impl,
  `TurboKeyValueDatabase`.
- **`BackingStorage` + `BackingStorageSealed`** — folded into inherent
methods on
  the renamed concrete type `TurboBackingStorage` (was the
  `KeyValueDatabaseBackingStorage<T>` generic + a type alias).
- **`ConcurrentWriteBatch`** — folded into inherent methods on
`TurboWriteBatch`
  (it existed to support an old backend implementation that's gone).
- The **`B: BackingStorage` generic parameter** is dropped from
`TurboTasksBackend`, `TurboTasksBackendInner`, `ExecuteContextImpl`, and
`ChildExecuteContextImpl`. Every instantiation in the workspace already
  resolved to one concrete type.

The only remaining trait in the crate's public role is `Backend`
(defined in
`turbo-tasks`), which stays — removing it would invert the
`turbo-tasks` → backend dependency.

**`Arc` indirections removed:**

- `TurboKeyValueDatabase.db: Arc<TurboPersistence>` → `TurboPersistence`
  (the write batch only borrowed it; the clone in `new` was gratuitous).
- The `self: &Arc<Self>` receivers and `self.clone()` calls in
`run_backend_job`,
`idle_start`, and `try_read_task_output` — the deferred/background work
reaches
the backend through the pinned `turbo_tasks` handle instead, whose `Arc`
already
  keeps the backend alive.
- With no remaining sharer, the backend's own
`Arc<TurboTasksBackendInner>` and
the `TurboTasksBackend` newtype were collapsed: the inner fields now
live
directly in `TurboTasksBackend`, stored inline in `TurboTasks` (which
already
  shares it via its own `Arc<TurboTasks>`).

**Other cleanups surfaced by the above:**

- `noop_backing_storage()` is now an empty, read-only instance of the
real
`TurboPersistence` instead of a separate `NoopKvDb` impl — same concrete
type as
`turbo_backing_storage()`, so the `Either<…>` wrappers in napi and
turbopack-cli
  go away.
- `TurboTasksBackend::backing_storage()` accessor replaced with a
focused
`invalidate_storage(reason_code)` method, so the backend no longer leaks
its
  storage object.
- Dead code removed (`CellDependency::key`, `ExecuteContext::schedule` /
`suspending_requested`, the non-stats `execute_with_stats` variant) and
  feature/test-only items gated behind their corresponding `cfg`.

### Why?

The no-op storage work removed the last thing forcing these abstractions
to be
generic/dynamic, so the single-impl traits and the extra `Arc` layers
were pure
indirection. This is a self-contained simplification: behavior is
unchanged.

### Tradeoff

- One `Either`/match in every backing-storage method becomes one
`is_empty`
  atomic load (`Relaxed` `AtomicBool`) in `should_restore` — essentially
  equivalent.
- The backend is now stored inline in `TurboTasks` rather than behind a
pointer:
  one fewer indirection on every backend access, at the cost of a larger
  `TurboTasks` struct.

<!-- NEXT_JS_LLM_PR -->
2026-06-05 10:34:12 -07:00
Benjamin Woodruff 8bc7074612 Turbopack: Perform issue filtering without turbo-task functions (#93885)
The filtering logic is generally very cheap, many issues are
short-lived, and filtering is unlikely to trigger more than 1-2 turbo
tasks per issue.

Wrapping it in another task per issue doesn't make a ton of sense.
2026-06-05 10:15:48 -07:00
Joseph ff767a7237 docs: typos and errors outside docs, error, examples folders (#94472)
Net pass over markdown files outside docs, examples and errors.
2026-06-05 13:15:42 +02:00
Niklas Mischkulnig 4162a56b41 Turbopack: refactor ServerActionManifestAsset to be lazy (#94377)
Previously, `AppEndpoint::output` computed the manifest content eagerly.
This change moves it to the emit phase.
2026-06-05 09:23:22 +00:00
Luke Sandberg dc856d6c33 [turbopack] Optimize TaskInput implementations (#94202)
# What

Replace the `derive` macros for TaskInput with a proc macro
`[turbo_tasks::task_input]` (and analogous support inside of the
`turbo_tasks::value` macro.

Introduce a hand written `CloneResolved` future type for TaskInput
implementations that are always resolved.

# Why

This allows us to take advantage of the `NonLocalValue` marker trait. If
a type is `NonLocalValue` then `TaskInput` can have trivial
`is_resolved` and `resolve_input` implementations. This applies to the
vast majority of TaskInputs but the current `derive` implementation
always produces a recursive `async` `resolve_input` implementation but
it is rarely needed and rustc/llvm do a bad job of optimizing it.

## Code-size impact

`next-swc.darwin-arm64.node` — release profile, `pnpm swc-build-native
--release`, macOS aarch64.

| Metric | canary | this branch | Δ bytes | Δ % |
| --- | ---: | ---: | ---: | ---: |
| Raw `.node` | 124,494,592 | 124,217,280 | −277,312 | −0.22% |
| Stripped (`strip -x`) | 84,294,000 | 84,013,136 | −280,864 | −0.33% |
| Stripped + gzip -9 | 29,123,538 | 28,898,920 | −224,618 | −0.77% |

## Performance

A similar measurement of performance was neutral

| Metric | canary | temporary | Δ | % |
|---|---|---|---|---|
| wall time (s) | 42.659 ± 1.851 | 42.501 ± 2.028 | −0.158 | −0.4% |
| user time (s) | 294.322 ± 1.834 | 293.207 ± 2.070 | −1.115 | −0.4% |
| sys time (s) | 74.158 ± 4.197 | 73.543 ± 5.725 | −0.615 | −0.8% |
| MaxRSS (MB) | 13650.1 ± 66.0 | 13610.1 ± 57.5 | −40.0 | −0.3% |

All deltas are smaller than one standard deviation — statistically
indistinguishable from noise on this benchmark, as expected for a small
optimization.
2026-06-03 23:26:17 +00:00
Tim Neutkens d342d0b91f Trace cacheHandler and cacheHandlers when using adapters (#94197)
### What?

Add focused production adapter coverage for configured `cacheHandler`
and `cacheHandlers` module graphs in Node `output.assets`, including
transitive helper dependencies and Turbopack `assetsHashes` assertions.

### Why?

Adapter deployments dynamically load configured cache handlers at
runtime. This coverage demonstrates that Node adapter outputs currently
omit those handler files and their transitive dependencies.

### How?

Add an App Router Cache Components fixture using `adapterPath`, both
cache-handler APIs, and a `use cache: remote` route. Its adapter writes
`onBuildComplete` context to disk and the test inspects Node outputs for
each handler and uniquely named helper.

### Verification

- `NEXT_TEST_PREFER_OFFLINE=1 pnpm --filter=next build`
- Expected failure: `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo
test/production/app-dir/adapter-cache-handlers/adapter-cache-handlers.test.ts`
(no Node output contains all four handler assets)
- Expected failure: `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack
test/production/app-dir/adapter-cache-handlers/adapter-cache-handlers.test.ts`
(same adapter handoff gap)
- `git diff canary --check`
- Not run continuously: `pnpm --filter=next dev` (`EMFILE: too many open
files` occurred after startup in the local watcher)

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
2026-06-03 12:24:54 +02:00
Niklas Mischkulnig d70b854294 Turbopack: fix pages index normalization for --debug-build-paths (#94341)
Recreation of https://github.com/vercel/next.js/pull/90314

---

## Summary
- Fix `--debug-build-paths` pages normalization in Turbopack so
`pages/<name>/index.tsx` maps to `/<name>` (matching JS
`getPageFromPath()` behavior).
- Handle index page edge case: `/index.tsx -> /`.
- Keep regression coverage in
`test/production/debug-build-path/debug-build-paths.test.ts` with a
generic fixture `pages/with-index/index.tsx`.

## Current Behavior Reference
- CI run (before this fix) that failed as expected for this bug:
-
https://github.com/vercel/next.js/actions/runs/22266469801/job/64413677099?pr=90314#step:35:216

## Root Cause
Rust-side debug-build-path normalization removed file extensions but did
not remove `/index`, producing route keys like `/with-index/index` while
pages route keys are `/with-index`. That mismatch filtered out the page
and caused `PageNotFoundError` during page-data collection.

---------

Co-authored-by: devjiwonchoi <devjiwonchoi@gmail.com>
2026-06-03 09:56:04 +02:00
Niklas Mischkulnig 0e6f52e99b Turbopack: fix bundle-analyzer for NFT change (#93363)
We have no testing infrastructure, unfortunately

<img width="2254" height="1260" alt="Bildschirmfoto 2026-05-28 um 20 29
58"
src="https://github.com/user-attachments/assets/a9f1b646-994d-4417-98d7-160ce5249032"
/>
2026-06-02 18:24:38 +02:00
Niklas Mischkulnig 2ae01a843e Turbopack: show codeframe when tracing too much (#94207)
Finally, we can show a codeframe
<img width="1204" height="774" alt="Bildschirmfoto 2026-05-28 um 21 16
35"
src="https://github.com/user-attachments/assets/a70c1fce-8a86-472a-9904-28f181d32385"
/>
2026-06-01 16:14:59 +02:00
Niklas Mischkulnig 87ee4fe431 Turbopack: refactor NFT to add Endpoint.traced_files (#94224)
Refactor the module-graph bit of NFT so that we can provide
`Endpoint.traced_files()` which is needed for the bundle analyzer

There should be no logic change whatsoever
2026-06-01 14:57:41 +02:00
Sam Poder edd08d0844 Switch TotalOrderF64 to f64 in ConstantNumber to correctly handle NaN === NaN and 0 === -0 inside the Turbopack analyzer (#94172)
This is how JavaScript handles them:

<img width="112" height="78" alt="Screenshot 2026-05-27 at 2 08 08 PM"
src="https://github.com/user-attachments/assets/21313410-7600-489a-a8bb-4e76d8052d23"
/>

However, we were using `TotalOrderF64` which did not follow this. This
PR switches to us to using `f64` in `ConstantNumber`.

This PR also adds execution tests that were previously failing but now
pass. A large amount of changes in this PR are changes in snapshot
tests, eg:

```diff
 Constant(
     Num(
         ConstantNumber(
-                            TotalOrderF64(
-                                1.0,
-                            ),
+                            1.0,
         ),
     ),
 ),
```

The other use of `TotalOrderF64` was in `CompileTimeDefineValue` which
has now been switched to `serde_json::Number` in a stacked PR:
https://github.com/vercel/next.js/pull/94177.
2026-05-31 23:30:32 -07:00
Will Binns-Smith 8216e23a07 Turbopack: reduce hmr chunk list subscriptions (#94062)
Previously, we created chunk list register chunks for every reachable
chunk in the chunk graph on a page. Now, we only create one that
subscribes to all recursively reachable assets.

This has to pass around an explicit list of client references chunks as
those cannot be discovered via the chunk graph alone.

This results in a significant performance improvement when loading pages
with the dev server, improving performance of a 60s cold build in a
large app by about 10s.
2026-05-29 12:12:09 -07:00
Will Binns-Smith 88027c2120 turbopack tracing: add chunk_name to get HMR events span (#94061)
This adds the chunk's file path to the span as metadata.
2026-05-29 12:12:08 -07:00
Niklas Mischkulnig 5ee7005200 Turbopack: use module graph for NFT (#92901)
- [x] skip traced edges properly in all graph traversals
- [x] iter_nodes and enumerate_nodes include the traced modules
    - [x] Don't propagate is_self_async inside of traced references as a perf optimization
- [x] fix performance
- [x] validate that NFT JSON files are identical
- [x] ~~validate/adjust bundle analyzer output~~ will be in followup
- [x] audit `fn chunking_type()` changes

Benchmark:
```
* ced85b3108 - (2 weeks ago) refactor - Niklas Mischkulnig (HEAD -> mischnic/nft-module-graph, origin/mischnic/nft-module-graph)

TURBOPACK_PERSISTENT_CACHE=0 NEXT_TURBOPACK_TRACING= pnpm next build   334.96s user 44.57s system 788% cpu 48.152 total
TURBOPACK_PERSISTENT_CACHE=0 NEXT_TURBOPACK_TRACING= pnpm next build   343.39s user 42.61s system 809% cpu 47.659 total

* 5edf48238f - (77 minutes ago) v16.3.0-canary.32 - next-js-bot[bot] (HEAD, tag: v16.3.0-canary.32)

TURBOPACK_PERSISTENT_CACHE=0 NEXT_TURBOPACK_TRACING= pnpm next build   333.42s user 43.57s system 712% cpu 52.877 total
TURBOPACK_PERSISTENT_CACHE=0 NEXT_TURBOPACK_TRACING= pnpm next build   332.95s user 43.15s system 791% cpu 47.515 total
TURBOPACK_PERSISTENT_CACHE=0 NEXT_TURBOPACK_TRACING= pnpm next build   337.59s user 41.28s system 771% cpu 49.117 total
TURBOPACK_PERSISTENT_CACHE=0 NEXT_TURBOPACK_TRACING= pnpm next build   335.25s user 45.53s system 718% cpu 52.975 total
```

<details>
<summary>Previous benchmarks</summary>

```
52212418699f362904d64c08e544d4994e8d27fa mischnic/nft-module-graph

pnpm next build   371.41s user 66.39s system 737% cpu 59.353 total
pnpm next build   380.13s user 59.62s system 751% cpu 58.548 total
pnpm next build   374.25s user 60.55s system 761% cpu 57.102 total

8e4cfc5062 canary

pnpm next build   370.77s user 53.89s system 762% cpu 55.725 total
pnpm next build   383.16s user 55.61s system 744% cpu 58.921 total
pnpm next build   382.34s user 53.51s system 757% cpu 57.512 total
pnpm next build   381.15s user 54.50s system 751% cpu 57.943 total
```


```
b0d5411a79 mischnic/nft-module-graph
pnpm next build --experimental-build-mode=compil  368.75s user 62.05s system 749% cpu 57.445 total
pnpm next build --experimental-build-mode=compil  368.95s user 58.42s system 763% cpu 56.004 total

0e3ad2768bd3b036e96f66e4b770a16bb0b38705 mischnic/nft-module-graph
pnpm next build --experimental-build-mode=compil  369.48s user 60.62s system 748% cpu 57.494 total
pnpm next build --experimental-build-mode=compil  374.89s user 52.27s system 730% cpu 58.449 total
pnpm next build --experimental-build-mode=compil  375.02s user 52.89s system 747% cpu 57.274 total

f37073e1d9 mischnic/speed-next-dynamic-map
pnpm next build --experimental-build-mode=compil  389.58s user 55.98s system 704% cpu 1:03.21 total

e599b11e2d6b09a92600cfca6d9b338ed1f7537d mischnic/fix-side-effect-edge-removal
pnpm next build --experimental-build-mode=compil  386.60s user 53.34s system 720% cpu 1:01.09 total

f68f70b044 canary
pnpm next build --experimental-build-mode=compil  387.46s user 53.72s system 738% cpu 59.716 total
pnpm next build --experimental-build-mode=compil  390.33s user 53.54s system 734% cpu 1:00.43 total
pnpm next build --experimental-build-mode=compil  385.33s user 56.55s system 722% cpu 1:01.18 total
pnpm next build --experimental-build-mode=compil  386.49s user 56.44s system 726% cpu 1:00.96 total
```



```
b1ed4553e6 origin/mischnic/nft-module-graph
pnpm next build --experimental-build-mode=compile  467.28s user 75.24s system 791% cpu 1:08.56 total
pnpm next build --experimental-build-mode=compile  465.00s user 76.80s system 831% cpu 1:05.20 total
pnpm next build --experimental-build-mode=compile  473.86s user 70.73s system 790% cpu 1:08.90 total

3cb77c844b canary
pnpm next build --experimental-build-mode=compile  367.91s user 58.06s system 726% cpu 58.672 total
pnpm next build --experimental-build-mode=compile  362.97s user 57.68s system 746% cpu 56.379 total
pnpm next build --experimental-build-mode=compile  364.59s user 57.01s system 724% cpu 58.167 total
```

</details>
2026-05-28 20:10:34 +02:00
Niklas Mischkulnig 4797acb82d Turbopack: fix instrumentationClientInject with type:module (#94184)
If the user's `package.json` contains, `type: module`, then the file
generated by Turbopack at `[project]/__next_instrumentation_client.js`
would get treated as ESM. But it always contains CJS, causing
```
Uncaught (in promise) ReferenceError: module is not defined
    at module evaluation (__next_instrumentation_client.js:3:1)
```
2026-05-28 10:28:00 +02:00
Tobias Koppers fda8a605dd ServerActionsGraph: pass ModuleGraphLayer as OperationVc (#94141)
### What?

Threads the layer module graph through `ServerActionsGraph` and
`map_server_actions` as an `OperationVc` instead of a resolved `Vc`,
calling `connect()` at each consumer.

### Why?

Fixes the invalidation priority of `map_server_actions` so it is
executed after the module graph on incremental builds. Previously,
because the graph was resolved at construction time, the dependency
ordering caused `map_server_actions` to be re-run before the module
graph on incremental rebuilds.

### How?

By passing the layer graph as an `OperationVc` and calling `connect()`
within each consumer task, the graph is resolved within the consuming
task rather than at construction time. This restores the intended
invalidation/scheduling order between the module graph and
`map_server_actions`.

<!-- NEXT_JS_LLM_PR -->
2026-05-27 03:57:02 +02:00
Tobias Koppers 1b77dba691 Turbopack: Fix unsound IntoIterator for ReadRef<T> (#94122)
### What?

Replaces the unsound by-value `IntoIterator` impl for `ReadRef<T>` in
`turbo-tasks` with a sound clone-free variant, and adapts the callers
across `turbopack-*` and `next-api`.

### Why?

The previous impl used `transmute_copy` to fabricate `&'static`-typed
items so it could expose them through the standard `Iterator` trait.
Those references were only really valid as long as the `ReadRef` inside
the iterator stayed alive — but `Iterator::Item` is a fixed associated
type, so once the items were stashed in futures, `Vec`s, or `serde_json`
map keys, the lifetime was completely unenforced.

This produced a latent use-after-free whenever something else
(turbo-tasks cell eviction, an intermediate `Drop`, etc.) released the
underlying storage between the iteration site and the next dereference.
The observed symptom was a panic in `RcStr::as_str` during JSON
serialization of `AssetHashesManifestAsset`'s manifest:

```
thread 'tokio-rt-worker' panicked at turbopack/crates/turbo-rcstr/src/lib.rs:132:52:
range end index 13 out of range for slice of length 7
```

The byte read at the inline-length position was junk left over from
freed/reused memory — `len = 13` is unreachable for any
legitimately-constructed inline `RcStr` (max inline length is 7 on
64-bit). The bug site was
`crates/next-api/src/project_asset_hashes_manifest.rs`, which consumed
an `OutputAssetsWithPaths` `ReadRef`, kept `&RcStr` references in
`asset_paths` past the `try_join` that dropped the iterator, then
serialized them.

### How?

**`turbopack/crates/turbo-tasks/src/read_ref.rs`** — new by-value impl:

```rust
pub struct ReadRefIter<T, I, J>
where
    T: VcValueType,
    I: Copy + 'static,
    J: Iterator<Item = &'static I>,
{
    iter: J,
    _read_ref: ReadRef<T>,
}

impl<T, I, J> Iterator for ReadRefIter<T, I, J> /* … */ {
    type Item = I;
    fn next(&mut self) -> Option<I> { self.iter.next().copied() }
}

impl<T, I, J> IntoIterator for ReadRef<T>
where
    T: VcValueType,
    I: Copy + 'static,
    J: Iterator<Item = &'static I> + 'static,
    &'static VcReadTarget<T>: IntoIterator<Item = &'static I, IntoIter = J>,
{
    type Item = I;
    type IntoIter = ReadRefIter<T, I, J>;
    fn into_iter(self) -> Self::IntoIter {
        let r: &VcReadTarget<T> = &self;
        // SAFETY: the fabricated `&'static` reference is only stored inside
        // `iter`, which lives inside the returned `ReadRefIter` alongside
        // the `ReadRef` that owns the data. `next()` only ever yields
        // `Copy`-ed-out values — no reference (with the fake `'static`
        // lifetime or otherwise) ever leaves the iterator. Struct-field drop
        // order (`iter` then `_read_ref`) drops the borrow before the
        // backing storage.
        let r = unsafe { std::mem::transmute::<&VcReadTarget<T>, &'static VcReadTarget<T>>(r) };
        ReadRefIter { iter: r.into_iter(), _read_ref: self }
    }
}
```

Key properties:
- **No cloning.** Setup is one borrow + `transmute`; `next()` is
`Option::copied()` (bitwise copy via the `Copy` bound), not
`Clone::clone`. Nothing in the iterator clones the backing collection or
its elements.
- **Contained `unsafe`.** The fake `'static` reference never leaves
`ReadRefIter`. `Iterator::next` yields `I` by value, so the lifetime
never escapes into futures, `Vec`s, or other persistence outside the
iterator.
- **Drop order safe.** Struct fields drop in declaration order: `iter`
(and any borrows it holds) drops before `_read_ref` (the backing `Arc`).
- **`Copy` bound.** The impl is restricted to element types that are
`Copy` — `ResolvedVc<_>`, integer ids, owned-tuple-of-`Copy`, etc. For
non-`Copy` element types (`RcStr`, `FileSystemPath`, `PatternMatch`,
`(String, _)`, `(ModuleId, ReadRef<_>)`, …) callers iterate by reference
via the existing `IntoIterator for &'a ReadRef<T>` impl (`for x in
&read_ref` or `read_ref.iter()`).

The original buggy site in `project_asset_hashes_manifest.rs` now uses
`output_assets.iter()` and keeps `&'a RcStr` references in the manifest
struct. The borrow checker now enforces the lifetime that used to be
faked via `transmute` — `output_assets` outlives the references because
nothing consumes it, and there are no clones at the call site either.

**Caller adjustments.** Touching the impl forced a sweep of all call
sites that were implicitly leaning on the unsound shape (yielding
`&'static`-typed items as a stand-in for owned items). The fixes fall
into a small number of categories:

- Drop redundant `.copied()` / `.cloned()` / `|&x| f(x)` patterns after
`into_iter()` (items are owned `Copy` values now, no need to
deref-and-copy).
- Switch non-`Copy` element iteration to `&read_ref` / `read_ref.iter()`
(e.g. `PatternMatches`, `CodeAndIds`, `UnresolvedUrlReferences`,
`GraphEntries`, `Vec<RcStr>`).
- Reshape `crates/next-api/src/paths.rs` helpers from `impl
IntoIterator<Item = &ResolvedVc<_>>` to `impl IntoIterator<Item =
ResolvedVc<_>>` — `ResolvedVc` is `Copy`, so by-value is the natural
shape and it composes directly with the new by-value
`ReadRef::into_iter`. Callers in `app.rs`, `pages.rs`, `middleware.rs`,
`instrumentation.rs`, `font.rs` updated to match (either passing the
`ReadRef`/`Vec` directly, or `.iter().copied()` for borrowed sources).
- A few small follow-ups: `for (key, EndpointGroup { primary, .. }) in
&entrypoint_groups` in `routes_hashes_manifest.rs` (with a borrowed `&'l
str` key in the manifest); `compute_async_module_info_single(graph,
result)` (no `*graph`, it's already `Copy`); `&(ty, batch)` → `(ty,
batch)` destructures in `chunking/mod.rs`.

### Testing

- `cac` clean across the workspace.
- `ca clippy --all-targets` clean.
- `ca test -p turbo-tasks-backend` — all unit + integration tests pass.
- `ca test -p turbopack-tests --tests` — execution snapshot suite (218
passed, 0 failed, 1 ignored) and snapshot suite (87 passed, 0 failed).

Closes NEXT-
Fixes #
2026-05-26 08:40:30 -07:00
Luke Sandberg ceff72681e Turbopack: don't crash the dev server when node_modules/next is briefly unresolvable (#93877)
### What?

When `node_modules/next` is briefly unresolvable mid-session (for
example because pnpm is mid-install of a package with a `next` peer
dependency, reshuffling its symlinks) Turbopack would:

- Emit a `MissingNextFolderIssue` with severity `Fatal`
- Propagate `anyhow!("Next.js package not found")` through ~10 levels of
the build graph
- Surface the failure at the napi boundary as a
`TurbopackInternalError`, which the Next.js JS-side treats as fatal and
shuts the dev server down

The reported user impact was a dev session that became wedged and could
not recover even after restarting `next dev`, because the persistent
cache had stored the failed operation state. Users reported it as
"stuck", "catastrophic", and required wiping `.next/` to recover.

Unfortunately while i was able to reproduce a number of bad outcomes, i
couldn't actually reproduce the issue of `next dev` getting stuck in a
bad state. Still this PR will improve a number of apis and error
messages

See internal discussion:
https://vercel.slack.com/archives/C046HAU4H7F/p1778792876550309

### Why?

A transient filesystem race during an install is the canonical
recoverable error — once the install finishes, `next` is resolvable
again. There is no reason this should bring down the dev server or
poison the persistent cache.

The bug had two contributing causes:

1. The error message was poor. It assumed the only cause was a mis-set
`turbopack.root`, with the entire message in `title()`. It didn't
acknowledge the install-race case or any of the other realistic causes
(broken symlink, monorepo hoisting, removed `node_modules/next`, global
install). Users had no way to know that the right response was to wait
or refresh.

2. The Rust→napi boundary did not treat this as a recoverable error. Two
specific code paths propagated `Err` instead of catching it:
- `issue_filter_from_endpoint` in `endpoint.rs` calls
`endpoint_op.connect().await?` — if the upstream endpoint resolution
errors (as it does when `directory_tree_to_loader_tree` fails), this `?`
propagates **before** the surrounding
`strongly_consistent_catch_collectables` gets a chance to convert the
failure into Issues.
- `Project::hmr_version_state` is called bare from `project_hmr_events`
at the napi boundary. There is no `_with_issues_operation` wrapper, so
any error from the underlying
`hmr_content`/`versioned_content_map.compute_entry` chain propagates
straight to JS.

### How?

Three changes:

**1. Rewrite `MissingNextFolderIssue`**
([next_import_map.rs](crates/next-core/src/next_import_map.rs))
- Severity downgraded from `Fatal` to `Error`. `Fatal` triggers an
explicit JS-side server shutdown in `print-build-errors.ts`; `Error`
lets the dev server recover.
- Title is a single line: `"Could not find the Next.js package
(next/package.json)"`.
- Description lists all realistic causes (concurrent install,
missing/broken symlink, mis-set workspace root, monorepo hoisting,
global install).
- Doc link moved to `documentation_link()` so it doesn't pollute the
inline message.

**2. Make `issue_filter_from_endpoint` Err-tolerant**
([endpoint.rs](crates/next-napi-bindings/src/next_api/endpoint.rs))
- Wrap the `endpoint_op.connect().await` in a `match` and fall back to
`IssueFilter::warnings_and_foreign_errors()` on `Err`.
- This unblocks the existing `strongly_consistent_catch_collectables`
recovery path, which then absorbs the upstream failure as Issues that
surface through the napi boundary normally.

**3. Add `hmr_version_state_with_issues_operation`**
([project.rs](crates/next-napi-bindings/src/next_api/project.rs))
- Mirrors the existing `hmr_update_with_issues_operation` pattern.
- Drives `hmr_version_operation` as an `OperationVc`, catches any `Err`
and falls back to `NotFoundVersion`, and collects issues from the
operation via `peek_issues`.
- The napi `project_hmr_events` handler now uses this wrapper instead of
calling `Project::hmr_version_state` bare. Issues from both
`hmr_version_state` and `hmr_update` are merged before being sent to JS.
- Required lifting the inner `hmr_version_operation` out of
`Project::hmr_version_state`'s closure to make it callable from the napi
layer.

A new regression test at
[test/development/app-dir/concurrent-install/](test/development/app-dir/concurrent-install/)
reproduces the failure by moving `node_modules/next` aside
mid-HMR-session and asserts that the dev server does not emit `FATAL: An
unexpected Turbopack error occurred` or `TurbopackInternalError`, and
that the friendly Issue text surfaces. The test is skipped under
`NEXT_SKIP_ISOLATE=1` since that mode doesn't produce a manipulable
`node_modules`.

Out of scope: after recovery, the dev server still returns 500 on
subsequent requests until the user manually refreshes (the JS-side
per-route error state isn't invalidated by Turbopack's successful
recompile). That's a separate dev-server caching issue.

<!-- NEXT_JS_LLM_PR -->
2026-05-21 18:34:27 +00:00
Niklas Mischkulnig 55b2d3c744 Upgrade rustc to nightly-2026-05-15 (#94021) 2026-05-21 20:26:11 +02:00
Benjamin Woodruff 80cb3447c1 [ci] Add an TURBO_TASKS_AVAILABLE_PARALLELISM environment variable that overrides std::thread::available_parallelism callsites (#93995)
We want to use this to potentially improve efficiency in e2e tests where
we're running many instances of Turbopack at once. By default,
turbo-tasks will try to use every available CPU core.

**This environment variable does not strictly limit Turbopack to using 4
cores, it just restricts the number of main tokio worker threads and
changes how we shard dashmaps.**

This option will not make sense for the vast majority of users, it only
makes sense when you are running *many* instances of Turbopack at once,
and the exact semantics are potentially confusing, as it is not a hard
limit.

## Test Plan

Build vercel-site with this set to 1 and see a little over 100% CPU
usage. Set it to 8 and see around 800% CPU usage.

```
TURBO_TASKS_AVAILABLE_PARALLELISM=1 pnpm next build --experimental-build-mode=compile
```
2026-05-20 17:14:48 -07:00
Luke Sandberg 21d47a456a Enable mimalloc in benchmarks (#93388)
this brings them closer to production
2026-05-19 18:31:51 -07:00
Luke Sandberg f7dee1b8ec [turbopack-trace-server] Add memory samples to the mcp tool results (#93338)
Expose memory data to the MCP tool for the turbo trace server
2026-05-19 22:05:34 +00:00
Josh Story 079df0f6f4 Enable rootParams by default (#93863)
rootParams is now available by default. the flag is removed.

There are still intentional limitations. For instance rootParams cannot
be used in route handlers and Server Actions. The feature will be
expanded with some support for this in the future.
2026-05-19 09:45:55 -07:00