1128 Commits

Author SHA1 Message Date
Tobias Koppers d6195eca8c Fix layout segment optimization: move app-page imports to server-utility transition (#91701)
## 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
2026-03-20 14:30:00 -07:00
Niklas Mischkulnig 6cb97d6d75 Turbopack: lazy require metadata and handle TLA (#91705)
A regression from #88487

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

This align with Webpack which does this:

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


Closes PACK-6927
Closes #91700
Closes https://github.com/vercel/next.js/issues/91676
2026-03-20 14:28:49 -07:00
Josh Story 0ca967b863 Add group depth tracking to instant validation boundary discovery (#91208)
The depth-based instant validation previously only iterated
URL-consuming segments as potential shared/new boundaries. Route groups
between URL segments were invisible — when a route group layout with
Suspense was shared in a client navigation, its Suspense appeared to
cover blocking code in the validation render even though it wouldn't in
reality.

This adds a second dimension to the validation iteration: group depth.
At each URL depth, we now also step through route group boundaries. A
new `discoverValidationDepths` function walks the LoaderTree and returns
an array where each entry represents the max group depth at that URL
depth. The validation loop iterates from deepest group depth to
shallowest within each URL depth, stopping on the first error.

Key design decisions:
- URL depth and group depth counters are advanced before the boundary
check, making the boundary condition a simple `nextUrlDepth > depth &&
currentGroupDepth >= groupDepth`
- The synthetic `(slot)` segment that Next.js inserts for parallel slots
is excluded from group depth counting since it doesn't represent a real
navigation boundary
- Group depths are discovered from the LoaderTree rather than the URL
pathname, making the tree the source of truth for validation bounds
2026-03-17 22:14:19 -07:00
Tobias Koppers ee1f9d79ad Remove webpack stats generation from Turbopack (#91515)
### 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>
2026-03-17 13:58:51 -07:00
Jiwon Choi 66478522a8 Improve RSC error messages (#90702)
Improve the RSC error messages to be more comprehensive.

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

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-17 14:37:26 +01:00
Benjamin Woodruff 924d39dcef docs(turbopack): Fix all warnings in cargo doc --no-deps --workspace, and run rustdoc as part of our CI checks (#91339)
This gets `cargo doc` clean, and enables it in CI so that we won't regress it (I'm open to removing this if it proves to be annoying).

I also cleaned up some documentation and even found some dead code (deleted `turbopack/crates/turbopack-dev-server/src/source/conditional.rs`).
2026-03-16 16:42:24 -07:00
Will Binns-Smith 0f867bb219 Turbopack: Enable server HMR by default for app pages (#91476)
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
2026-03-16 16:37:52 -07:00
Jiwon Choi fa32daad8d Add unstable_catchError() API for custom error boundary (#89688)
This PR adds `unstable_catchError()` API for a granular custom error
boundary. The error component generated by this API is not a true
component format, as the wrapper provides additional args. Therefore,
the API is a function call by design rather than a boundary component to
avoid merging the `errorInfo` with the user-provided props from the
wrapper.

This API works for both the App/Pages Router. However, `retry()` is not
allowed in Pages Router as it depends on `router.refresh()`, and will
throw. Also, it's exported from `next/error` as there was already an
`Error` component for the [Pages
Router](https://nextjs.org/docs/pages/building-your-application/routing/custom-error#reusing-the-built-in-error-page).

### DevTools

<img width="508" height="90" alt="CleanShot 2026-03-13 at 03 02 46@2x"
src="https://github.com/user-attachments/assets/8b79b1f6-877d-4901-99f7-6a2b4d3e34fe"
/>

Docs to follow up: https://github.com/vercel/next.js/pull/89847
Closes NAR-768

---------

Co-authored-by: Josh Story <story@hey.com>
2026-03-16 22:57:11 +00:00
Niklas Mischkulnig 46789444d5 Remove unused layer from server actions manifest (#91425)
```
    // 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.
```
2026-03-16 14:10:22 +01:00
Tobias Koppers 261922df82 Show generated code from loaders in parse error messages (#89898)
## What?

When a webpack/turbopack loader produces broken code, error messages now
display **both** the original source and the generated code with source
map information, making it much easier to debug loader issues.

## Why?

Previously, when loaders returned invalid code, error messages only
showed the original source file (after source-map remapping). Users had
no way to see what the loader actually generated, making it hard to
diagnose why the code failed to parse. Showing both sides gives full
context about what went wrong.

## How?

### Turbopack Core (`turbopack-core`)

- **`Source::description()`** — New method on the `Source` trait
providing human-readable descriptions of where code comes from.
Implemented across all source types (`FileSource`, `VirtualSource`,
`WebpackLoadersProcessedAsset`, `PostCssTransformedAsset`, etc.),
producing chains like `"loaders [sass-loader] transform of file content
of ./styles.scss"`.
- **`AdditionalIssueSource`** — New struct to hold a labeled source
location. The `Issue` trait gains an `additional_sources()` method so
issues can expose supplementary code frames.
- **`GeneratedCodeSource`** — A wrapper that strips `GenerateSourceMap`
support from a source, ensuring the *generated* code is displayed as-is
rather than being remapped back to the original.
- **`IssueSource::to_generated_code_source()`** — Helper that detects
sources implementing `GenerateSourceMap` and wraps them in
`GeneratedCodeSource` for display. Used by `AnalyzeIssue` and
`ParsingIssue` to automatically attach generated code frames.

### Error Formatting

- **`turbopack-cli-utils`** — Renders additional sources in CLI issue
output.
- **`format-issue.ts`** — Renders additional sources in the browser
error overlay. Extracted `formatSourceCodeFrame()` helper to deduplicate
code-frame rendering between primary and additional sources.
- Long-line truncation (e.g. minified CSS from SCSS) is handled natively
by the Rust-based `codeFrameColumns` implementation.

### Type Definitions

- Added `SourcePosition`, `IssueSource`, and `AdditionalIssueSource`
interfaces to TypeScript types.
- Updated `PlainSource` (added `file_path`), `PlainIssue` (added
`additional_sources`), and NAPI bindings to pass the data through.

### Test Coverage

- **E2e tests** (`test/e2e/webpack-loader-parse-error/`) with custom
broken JS and CSS loaders, covering all 4 modes:
- **Development (Turbopack)** — Verifies parse errors show both original
and generated code via browser error overlay
- **Development (Webpack)** — Verifies error overlay shows the parse
error (webpack doesn't support additional sources)
- **Production (Turbopack)** — Verifies build failure output with full
error extraction and inline snapshots
- **Production (Webpack)** — Verifies build failure output with inline
snapshots
- Updated `test/development/sass-error/` snapshot to include the new
generated code frame for minified SCSS output.

### Example Output

When a loader produces broken code, users now see:
```
⨯ ./app/data.broken.js:3:1
Parsing ecmascript source code failed
  1 | // This file will be processed by broken-js-loader
  2 | // The loader will return invalid JavaScript with a source map
> 3 | export default function Data() {
    | ^
  4 |   return <div>original source content</div>
  5 | }
  6 |

Expected '</', got '{'

Generated code of loaders [./broken-js-loader.js] transform of file content of app/data.broken.js:
./app/data.broken.js:3:46
  1 | // Generated by broken-js-loader
  2 | export default function Page() {
> 3 |   return <div>this is intentionally broken {{{ invalid jsx
    |                                              ^
  4 | }
  5 |

Import trace:
  Server Component:
    ./app/data.broken.js
    ./app/page.js
```

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <lukeisandberg@gmail.com>
2026-03-16 13:41:29 +01:00
Niklas Mischkulnig d9751eaa14 Turbopack: use ChunkGroupEntry::Shared, part 2 (#91279)
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.
2026-03-16 12:02:31 +01:00
Luke Sandberg 2e4f08ca0a fix(build): don't block SSG on telemetry flush, add persistence spans to trace-build (#91335)
### What?

Two fixes for the Turbopack build tracing introduced in #90397:

1. **Don't block SSG on Turbopack shutdown**: `workerMain()` no longer awaits the shutdown promise before returning. Trace event collection is deferred to `waitForShutdown()`, which the parent process awaits *after* SSG completes. This allows static generation and Turbopack persistence/cache-flush to run in parallel.

2. **Add persistence spans to `trace-build` allowlist**: `turbopack-build-events`, `turbopack-persistence`, and `turbopack-compaction` are now included in the `to-json-build.ts` allowlist so they appear in `.next/trace-build`.

### Why?

- The `await shutdownPromise` in `workerMain()` was too eager — it prevented the caller from acknowledging the build as complete and starting SSG until Turbopack persistence finished flushing to disk.
- The persistence/compaction spans emitted by Rust (`turbopack-persistence`, `turbopack-compaction`) were not in the `to-json-build.ts` allowlist, so they were silently filtered out of `.next/trace-build`.

### How?

**`impl.ts` (worker)**:
- Removed `await shutdownPromise` from `workerMain()` — it now returns build results immediately
- `waitForShutdown()` now returns `{ debugTraceEvents }` after awaiting shutdown, so trace events are collected only after all compilation events (including persistence spans) have been processed

**`index.ts` (parent)**:
- Moved `recordTraceEvents(debugTraceEvents)` from the `workerMain` result handler into the `shutdownPromise` `.then()` chain, so events are replayed into the parent reporter after shutdown completes

**`to-json-build.ts`**:
- Added `turbopack-build-events`, `turbopack-persistence`, `turbopack-compaction` to the allowlist

**Test updates**:
- Enabled `turbopackFileSystemCacheForBuild: true` in the trace-build test fixture
- Updated the Turbopack inline snapshot to include `turbopack-build-events`
2026-03-15 23:42:17 -07:00
Matt Mastracci 2b4bde2ebb Enable thin LTO for release builds (#91343)
# What

Add `lto = "thin"` to the workspace-level `[profile.release]` in
`Cargo.toml`.

# Why

Thin LTO enables cross-crate optimization at link time, improving
runtime
performance of the final artifacts (next-napi-bindings native module,
turbopack-cli binary, and benchmarks) without the full build time cost
of
fat LTO.

# How

Added `lto = "thin"` to the workspace root `[profile.release]` section.
This applies to all release-profile link targets in the workspace,
including
binaries, cdylibs, and benchmarks. The `release-with-assertions` and
`release-with-debug` profiles inherit from `release`, so they also
benefit.
2026-03-14 12:49:43 -06:00
Niklas Mischkulnig 202db657d8 Turbopack: don't emit polyfill chunk for API routes (#91331)
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.
2026-03-13 13:58:16 -07:00
Tobias Koppers e22988e5ee Turbopack: switch chunk/asset hashes from hex to base40 encoding (#91137)
### What?

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

### Why?

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

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

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

### How?

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 20:38:02 +00:00
Luke Sandberg 236a76dd0f [turbopack] Remove turbo_tasks::function from ModuleReference getters (#91229)
### 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
2026-03-13 11:54:56 -07:00
JJ Kasper 07189c5224 Expose edge runtime fields in build-complete (#91239)
### What?

- Add canonical edge entrypoint metadata to edge function definitions in
the middleware manifest.
- Surface that canonical entrypoint through `build-complete` as
`edgeRuntime` metadata in adapter outputs.
- Update middleware and adapter tests to assert the new metadata instead
of relying on bundler-specific emitted file names.

### Why?

Adapter consumers need a stable way to load and invoke edge outputs
without depending on `next-server`'s sandbox handling or guessing which
emitted file is the executable entrypoint. The existing checks were also
brittle across webpack and Turbopack because the emitted file lists can
differ.

### How?

- Add an optional `entrypoint` field to `EdgeFunctionDefinition` and
populate it in both the webpack middleware manifest generator and the
Turbopack manifest emitters for app, pages, and middleware edge
functions.
- In `packages/next/src/build/adapter/build-complete.ts`, use that
canonical entrypoint for `filePath` and expose `edgeRuntime.modulePath`,
`edgeRuntime.entryKey`, and `edgeRuntime.handlerExport` for edge
outputs.
- Adjust the middleware manifest tests to validate cross-bundler
invariants and add adapter-config coverage for the new `edgeRuntime`
fields.
2026-03-13 18:22:47 +00:00
Benjamin Woodruff d7de6f105c Turbopack: Remove the IntoTraitRef trait, make it an inherent method on Vc (#91223)
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.
2026-03-13 10:43:28 -07:00
JJ Kasper a6b36200ff Wire cache handlers in edge paths and add e2e regression coverage (#91236)
### What?

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

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

### Why?

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

### How?

- pass `cacheHandler` / `cacheHandlers` through the JS build entry
plumbing for edge server entries
- inject cache handler imports/registration into the webpack edge
templates and loaders
- mirror the same wiring in the Turbopack/Rust entry generation for edge
app pages, app routes, pages SSR, and middleware
- update the edge route wrapper to initialize and register cache
handlers before invoking the route module
- extend `next-taskless` template expansion with raw injection support
so the generated edge templates can add imports and registration code
- add `test/e2e/cache-handlers-upstream-wiring` fixtures to cover
pages/app, edge/non-edge, and revalidation behavior
2026-03-13 17:25:59 +00:00
Niklas Mischkulnig c1f13e48b0 Turbopack: rename CSS module structs for clarity (#91304) 2026-03-13 17:00:37 +01:00
Luke Sandberg 04a08435e2 Remove LMDB backend and ReadTransaction abstractions from turbo-tasks-backend (#91284)
## Summary

Remove the unused LMDB database backend and the `ReadTransaction` abstraction layer from `turbo-tasks-backend`. This is a pure cleanup that deletes ~1,500 lines of code and eliminates all `unsafe` blocks related to transaction lifetime management.

**Motivation:**
- This was much more useful in the early days of the persistence layer where comparing against a 'known good' db was useful, but this has rotted and turbo-persistence has gained lots of tests and usage so that this is not needed.
- Carrying the code added a lot of complexity to APIs e.g.
    - The `ReadTransaction` abstraction existed solely to support LMDB's transaction-based read model
    - The only remaining backend (`TurboKeyValueDatabase` via `turbo-persistence`) already used `()` as its `ReadTransaction` type, making the entire abstraction dead weight
    - The transaction plumbing required `unsafe` transmute-based lifetime extension in `ExecuteContextImpl`, adding complexity and risk for no benefit
    - The WriteBatch::Serial branches complicated already subtle code paths
2026-03-12 16:19:42 -07:00
Will Binns-Smith 5809756f55 bundle-analyzer: merge _app route modules into current route visualization (#90841)
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
2026-03-12 14:52:36 -07:00
Janka Uryga 7bbea9d4b1 [Instant] Build-time validation (#90964)
Initial version of build-time instant validation.

During prerendering, if we have `unstable_instant` configs, we perform a
complete staged render of the page (including dynamic data). This render
mocks request data (`params`/`searchParams`/`cookies`/`headers`) using
the `samples` provided in `unstable_instant`. Then, we use the staged
chunks to run instant validation in mostly the same way that we would in
dev. Most changes in the validation flow are related to samples, which
also need to be passed into the client render to mock APIs like
`useParams`.

### Providing Mock data

We run everything in a fresh workStore+workUnitStore to (hopefully)
avoid any data from the prerender from sneaking in. This is quite
involved.

A big part of the trickiness is that if you do e.g. `(await
cookies()).get('myCookie')` but the `samples` don't contain `myCookie`,
we want the validation to error and require explicitly saying what state
`myCookie` is in.
If it's available, use
```ts
export const unstable_instant = {
  // ...
  samples: { cookies: [{ name: "myCookie", value: "someValue" }] }
}
```
If it's not available, use
```ts
export const unstable_instant = {
  // ...
  samples: { cookies: [{ name: "myCookie", value: null }] }
}
```
This explicitness makes it harder to e.g. unintentionally validate a
logged-out state.

Note that even validating a `prefetch: 'static'` page might require
samples, because we need the shared parent for the navigation to be
(mostly) complete. We might be able to relax this in the future, but
it's not straightforward.

Another issue is that a segment might access a missing sample in a
"benign" way, i.e. where it wouldn't block validation (or be inside a
suspense boundary). Ideally we wouldn't require providing a sample for
usages like that, but currently we do.

### Instrumenting APIs to detect missing samples

All request APIs are instrumented in this way.
- `params` throw when accessing a property that was not declared in
`samples`. same for the object from `useParams()`. `in` is allowed
because we know the shape of the params object statically, based on the
routing structure.
- `searchParams` throw when accessing a property that was not declared
in `samples` and `in` checks.
- objects returned from `cookies()`, `headers()`, `useSearchPrams()`
throw on `get()` and `has()`.

There's a lot of tests for this behavior in `instant-validation-build`.

One gap is that iteration/enumeration is currently not instrumented.
We're punting on it for now and will revisit in a follow-up.

### Relation to prerender

We kick off the validation after the prerender, in
`renderToHTMLOrFlightImpl`. Ideally, we should kick this off somewhere
higher up, separately from the prerender itself, but we're leaving that
for future work.

Note that if we do multiple prerenders for the same page (e.g. due to
`generateStaticParams`, we'll only run validation for the first
prerender for that route. `generateStaticParams` is not currently
integrated into the validation at all, i.e. we don't use those params to
validate, so this is safe. All params need to be provided via `samples`.
This will likely change in the future, but we're not sure how yet.

### Disabling build validation

Build-time instant validation is experimental, and has more potential
problems to deal with than dev validation. To allow incremental rollout
and testing, we allow disabling it per instant config:
```ts
export const unstable_instant = {
  unstable_disableBuildValidation: true
  // ...
}
```

for parity, there's also `unstable_disableBuildValidation`, but we don't
expect that to see much use.
2026-03-12 14:57:38 +01:00
zoomdong 41ed440b50 TURBOPACK: typescript transform support verbatimModuleSyntax (#90907)
In utoo case, we need to support react classic jsx transform(To
compitable with react 16 or above project).

We have a case like:

```ts
// index.ts
import React from 'react';

export const App = () => <div></div>;
```

Under the classic jsx transform, the jsx will be transformed as follows:

```ts
export const App = () => React.createElement("div", /***/);
```

But turbopack's jsx transform was behind typescript transform, so in the
stage of typescript transform, the import of react will be deleted by
the default config of swc typescript transform(which set
`verbatimmodulesyntax` as false by default):
https://swc.rs/docs/migrating-from-tsc#verbatimmodulesyntax-true

Then the output of classic jsx transform will get a runtime error like
`React was not found`.

For solve the problem, this pr just support a config
`verbatimModuleSyntax` for turbopack's typescript transform, it will not
remove user's React import under classic jsx transform.

We test the case under utoopack in the following pr:

- https://github.com/utooland/utoo/pull/2653
- https://github.com/utooland/utoo/pull/2655

And Rspack also enable `verbatimModuleSyntax` to true by default under
2.0:
https://github.com/web-infra-dev/rspack/discussions/9270#discussioncomment-13429924
2026-03-12 04:30:11 +00:00
Niklas Mischkulnig 196ed2b839 Fix syntax contexts in server action transform (#91210)
```
let x: Ident = IdentName::new(name, span).into();
```
should never be done. It uses `SyntaxContext::default()` for the Ident. So from swc's perspective, you are referring to a different variable than you think. When constructing an `Ident`, you should usually always provide a SyntaxContext.

I think this would pose a problem in Webpack because it serializes it into a code string again, but for Turbopack these SyntaxContext are important.
2026-03-11 22:16:01 +01:00
xusd320 b1db02697c fix(turbopack): unref ThreadsafeFunctions to allow Node.js exit after build (#91107)
## What?

Calls `unref()` on the `ThreadsafeFunction`s created in
`NapiNextTurbopackCallbacks::from_js` (`throw_turbopack_internal_error`
and `on_before_deferred_entries`).

## Why?

These `ThreadsafeFunction`s are not unref'd, which means they keep the
Node.js event loop alive even after `project_shutdown` completes.

Currently this doesn't cause a visible hang in Next.js because turbopack
runs inside a worker thread (via `packages/next/src/lib/worker.ts`), and
the worker thread is terminated externally. However, if turbopack were
ever run in a child process or directly in the main process, the build
would hang indefinitely after completion — the Rust side of
`stop_and_wait()` finishes, but the Node.js process never exits because
the ref'd `ThreadsafeFunction`s prevent the event loop from draining.
This happened in `utoo`, caused some CI hang forever, the same fix
commit:
https://github.com/utooland/utoo/pull/2670/changes/9e8fd08e84470dce6ac59e976db1b556ce7fece8.

This fix makes the shutdown behavior more robust by following the same
pattern already used in `register_worker_scheduler` in
`turbopack-node/src/worker_pool/worker_thread.rs`, where both creator
and terminator `ThreadsafeFunction`s are explicitly unref'd.

## How?

- Added `Env` parameter to `NapiNextTurbopackCallbacks::from_js()`
- Called `unref(&env)` on both `throw_turbopack_internal_error` and
`on_before_deferred_entries` after creation
- Updated the call site in `project_new` to pass `&env`
2026-03-11 19:05:52 +00:00
Niklas Mischkulnig 19ff1babf0 Lint against .context(format!(...)) (#91122)
There are unfortunately a few false-positives, because this isn't aware of type information of course.
2026-03-11 10:31:51 +01:00
Tobias Koppers 187f35f287 Add experimental.lightningCssFeatures config option (#90901)
### What?

Adds a new `experimental.lightningCssFeatures` config option that lets
users control which CSS features lightningcss should always transpile
(`include`) or never transpile (`exclude`), regardless of browserslist
targets.

```js
// next.config.js
module.exports = {
  experimental: {
    useLightningcss: true,
    lightningCssFeatures: {
      include: ['light-dark', 'oklab-colors'],
      exclude: ['nesting'],
    },
  },
}
```

### Why?

Currently, lightningcss feature transpilation is determined solely by
browserslist targets. There's no way to force transpilation of a
specific feature (e.g., `light-dark()`) when targeting modern browsers
that already support it, or to skip transpilation of a feature that the
user wants to preserve as-is.

This is useful for:
- Forcing polyfill-style transpilation for features with incomplete
browser support
- Testing transpiled output for specific features
- Opting out of specific transforms that may interfere with CSS tooling
downstream

### How?

**TypeScript config & validation:**
- `config-shared.ts`: `LIGHTNINGCSS_FEATURE_NAMES` const array (single
source of truth) with `LightningCssFeature` type derived from it, and
`LightningCssFeatures` interface
- `config-schema.ts`: Zod validation using
`z.enum(LIGHTNINGCSS_FEATURE_NAMES)` for both include/exclude arrays

**Feature name → bitmask mapping (Rust, shared via NAPI):**
- `crates/next-core/src/next_config.rs`:
`lightningcss_feature_names_to_mask()` maps feature name strings to
`lightningcss::targets::Features` bitflag constants and returns a
`Result<u32>` bitmask. Unknown feature names produce an error via
`bail!`.
- Exposed to JS via NAPI as `lightningcssFeatureNamesToMaskNapi`,
callable through `bindings.css.lightning.featureNamesToMask(names)`
- Single source of truth for feature name resolution — both webpack and
Turbopack paths use this Rust function

**Webpack path:**
- `global.ts` / `modules.ts` pass config through to loader options
- `loader.ts` calls `featureNamesToMask()` via the native bindings to
compute include/exclude masks, then passes them to the SWC `transform()`
call
- The `lightningCssFeatures` without `useLightningcss` warning only
shows when using webpack (Turbopack always uses lightningcss)

**Turbopack path (Rust):**
- `next_config.rs`: `LightningCssFeatures` struct for deserialization,
accessor methods converting names → u32 bitmask via
`lightningcss_feature_names_to_mask()`
- Config flows through `CssOptionsContext` → `ModuleType::Css` →
`CssModuleAsset` (using `LightningCssFeatureFlags { include, exclude }`
struct) → `process.rs` where bitmasks are merged into
`lightningcss::targets::Targets { include, exclude }`
- Uses `u32` bitmask representation throughout to avoid adding
lightningcss dependency to non-CSS crates

**Defaults preserved:** `Nesting | MediaRangeSyntax` for Turbopack,
`Nesting` for Webpack. User `include` is OR-ed on top, user `exclude`
masks bits off.

**Feature names:** 21 individual features (bit 0–20) + 3 composite
groups (`selectors`, `media-queries`, `colors`), all in dash-case.

### Test

- **Include test**
(`test/e2e/app-dir/experimental-lightningcss-features/`) — targets
Chrome 123 (which natively supports `light-dark()`) with `include:
['light-dark']`, then asserts the CSS output contains lightningcss
transpilation markers (`--lightningcss-light`, `--lightningcss-dark`)
instead of raw `light-dark()`.
- **Exclude test**
(`test/e2e/app-dir/experimental-lightningcss-features-exclude/`) —
targets Chrome 100 (which does NOT support `light-dark()` natively) with
`exclude: ['light-dark']`, then asserts the CSS output preserves raw
`light-dark()` calls and does not contain transpilation markers.
2026-03-11 08:06:52 +01:00
Luke Sandberg ff36c17c14 [turbopack] Render code frames on the turbopack side (#90534)
Not that error rendering performance is critical, but it is silly to pass frame information from turbopack to js just to have js pass the same information back to native code to draw a frame.
2026-03-10 18:52:40 -07:00
Niklas Mischkulnig c44578498f Turbopack: introduce ReferenceTypeCondition (#91101)
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<...>)`
2026-03-10 09:47:58 +01:00
Tobias Koppers 014b9987fa Turbopack: implement module.hot.accept(deps, cb) and module.hot.decline(deps) (#90443)
### 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'])`
2026-03-09 10:51:17 -07:00
Niklas Mischkulnig fda324e206 Turbopack: correctly use ChunkGroup::Shared (#90978)
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
2026-03-09 10:12:37 +01:00
Niklas Mischkulnig 99303d31fa Turbopack: accept ChunkGroup in entry_chunk_group (#90821)
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
2026-03-07 16:39:09 +01:00
Benjamin Woodruff 6e894cfc85 Turbopack: Make ReadVcFuture::strongly_consistent private (#90843)
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.
2026-03-06 11:36:38 -08:00
Niklas Mischkulnig 07df7fa028 Turbopack: pass reference type to CustomModuleType (#90814)
No reason to restrict it to `ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::ImportPart(part))`
2026-03-06 12:19:15 +01:00
Niklas Mischkulnig 73b44a185b Turbopack: CompileTimeDefineValue numbers and regex (#90934)
- Number(f64) instead of Number(RcStr)
- Add BigInt
- Add Regex
2026-03-06 09:48:23 +01:00
Tobias Koppers ad030d782e Turbopack: Tree shaking fixes and code organization (#89295)
## 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
2026-03-05 07:49:19 +01:00
Luke Sandberg 286a73f84a Add trace spans for turbopack persistence in .next/trace (#90397)
## Summary

- Add a generic `TraceEvent` compilation event type that carries a name, wall-clock timing, and arbitrary attributes
- Emit `TraceEvent` from Turbopack's Rust backend for cache persistence and compaction operations
- Expose `eventJson` on compilation events through the NAPI bridge
- Record `turbopack-persistence` and `turbopack-compaction` trace spans in `.next/trace` with memory usage snapshots, in both dev and build workflows

## Details

Turbopack persistence and compaction operations were invisible in `.next/trace`. The only signal was a console log for operations exceeding 10s.

A new generic `TraceEvent` type replaces the need for per-operation event structs. It carries a name, start/end wall-clock timestamps, and a `Vec` of key-value attributes. On the JS side, `backgroundLogCompilationEvents` handles all `TraceEvent`s with a single code path — creating trace spans via `manualTraceChild` and recording memory usage snapshots. Adding new traced operations requires only a few lines of Rust and zero JS changes.

At @bgw's suggestion, I considered integrating as a `tracing_subscriber::Layer` to automatically forward spans to the compilation event system, but this ended up requiring quite a bit of code (a new subscriber, 'target' impl, span lifecycle management, new crate dependencies) for what is currently just 2 events, and we don't really anticipate adding more.

## Test plan

- [x] Integration test in `test/e2e/filesystem-cache` verifying `turbopack-persistence` spans exist with correct attributes
- [x] `pnpm test-dev-turbo test/e2e/filesystem-cache`
- [x] `pnpm test-start-turbo test/e2e/filesystem-cache`
2026-03-04 16:33:25 -08:00
Niklas Mischkulnig a41e4d5702 Unify Node and Edge externals list (#90819)
Unify the list of Node/Edge iexternals.
Turns out `readline/promises` and `stream/consumers` was missing in some places.

Closes https://github.com/vercel/next.js/issues/90765
2026-03-04 18:36:03 +01:00
Niklas Mischkulnig 5b17dfeb80 Turbopack: allow resolving TS files in tailwind config (#90046)
A regression from https://github.com/vercel/next.js/issues/86576

Closes https://github.com/vercel/next.js/issues/87898
Closes PACK-6421
2026-03-04 18:33:17 +01:00
Sebastian "Sebbie" Silbermann cb44b43cdf Use page extensions in next-swc transforms applying to pages (#90650)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
2026-03-04 16:13:36 +00:00
Matt Mastracci 1bc8f55993 Add Err(anyhow(... ast-grep rule (#90869)
# What

Add an ast-grep rule to use `bail!` rather than `Err(anyhow!(` in a
return position.

# Why

This is slightly more compact, and allows us to port more code over to
turbofmt/turbobail.

Note: _this PR was previously merged into another branch due to a GH
bug_
2026-03-04 09:08:55 -07:00
Matt Mastracci e4cae37084 Turbopack: turbofmt/turbobail macros (#90092)
# 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
2026-03-04 15:11:04 +00:00
Luke Sandberg ce14ca88a4 Reimplement code frame rendering in native code (#85592)
## What

Replaces the `@babel/code-frame` dependency with a new Rust-based implementation (`next-code-[frame](https://github.com/arthurprs/qfilter/pull/20#issuecomment-3986882055)` crate) for rendering code frames in error messages.

### Why

- **Crash fix**: `@babel/code-frame` uses the `js-tokens` library for syntax highlighting, which has [known issues](https://github.com/lydell/js-tokens?tab=readme-ov-file#known-failures) with large string literals and long lines. This can cause Next.js to throw RangeErrors when rendering errors, hiding the original issue!
- **Long line support**: The old implementation had no concept of terminal width, dumping entire lines into the output. The new implementation uses "horizontal scrolling" — truncating lines and centering the error location in the visible window.
- **Performance**: The Rust implementation only processes the visible line range (typically ~6 lines), not the entire file. Syntax highlighting uses a skip-scan heuristic to start tokenizing near the visible window rather than from byte 0.
- **Dependency reduction**: Drops the semi-unmaintained `@babel/code-frame` bundled dependency in favor of code we control.

### Benchmarks

In-process benchmarks comparing `render_code_frame()` (Rust, via criterion) against `codeFrameColumns()` (Babel, via hrtime with DCE prevention). Both have syntax highlighting and color output enabled. No process startup or file I/O is included in the measurement.

| Scenario | `next-code-frame` (Rust) | `@babel/code-frame` | Speedup |
|---|---|---|---|
| Small file (~490 lines TSX) | **5.4 µs** | 507 µs | **~94x** |
| Large file (~39k lines JS) | **143 µs** | 82.9 ms | **~580x** |
| Large file minified | **51 µs** | - | **-** |

The gap widens with file size because Babel's `highlight()` runs a regex tokenizer over the **entire source** before slicing to the visible window, while the Rust implementation uses a windowed line index and skip-scan heuristic — only processing the visible window regardless of file size.  However, for minified files we do end up tokenizing the whole thing so we end up being only 8x faster

### How

- New `crates/next-code-frame/` Rust crate with:
  - Frame rendering with terminal-width-aware horizontal scrolling
  - Regex-based syntax highlighting (matching Babel's color scheme)
  - Skip-scan heuristic for O(1)-ish highlighting regardless of file size
  - Windowed line index that only scans/stores offsets for the visible region
  - Comprehensive test suite (800+ lines)
- Exposed via both NAPI (native) and WASM bindings
- JS wrappers in `packages/next/src/shared/lib/errors/`:
  - `code-frame.ts` — primary wrapper using native bindings
  - `optional-code-frame.ts` — graceful fallback returning `undefined` if bindings are unavailable
- All existing callsites (`diagnosticFormatter`, `parseScss`, dev overlay, turbopack utils) updated
- for `patch-error-inspect.ts` i adopted an `injection` style approach to avoid coupling to the native dependency

### Concerns / review focus areas

- **Reliability**: The regex-based tokenizer is best-effort and language-agnostic — it should never crash on invalid syntax, but highlighting accuracy may differ from Babel's `js-tokens` in edge cases.
- **Native dependency**: This moves code frame rendering into the native binary. Performance should be better, but worth verifying there are no regressions in environments where native bindings behave differently (e.g. WASM fallback path).
- **Regressions**: The output format and color scheme closely match Babel's, but there may be subtle differences. The horizontal scrolling behavior is new.

Fixes #85357
Closes PACK-5754
2026-03-03 04:59:11 -08:00
Tobias Koppers de1add2539 Remove unused turbo-tasks items and add lint check (#90801)
### 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.
2026-03-03 12:01:45 +01:00
Jan Nicklas 5fa40c806c exempt data URL CSS from Pages Router global CSS restriction (#89901)
### 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>
2026-03-03 11:17:30 +01:00
Tobias Koppers 420b72b697 Update Rust dependencies for turbo-persistence and turbo-tasks-backend (#90774)
### What?

Updates outdated Rust dependencies used by `turbo-persistence` and
`turbo-tasks-backend` crates.

### Why?

Keep dependencies current to benefit from bug fixes, performance
improvements, and security patches.

### How?

Each dependency was updated in a separate commit. All crates were
verified to compile and tests pass.

**Direct dependency updates:**
- `lzzzz` 1.1.0 → 2.0.0 (moved to workspace dependency, used by both
crates)
- `arc-swap` 1.7.1 → 1.8.2 (turbo-tasks-backend)

**Workspace dependency updates:**
- `bitfield` 0.18 → 0.19
- `either` 1.9 → 1.15
- `indexmap` 2.7 → 2.13
- `once_cell` 1.17 → 1.21
- `rand` 0.9 → 0.10 (required API migration: `Rng`/`RngCore` → `RngExt`)
- `ringmap` 0.1 → 0.2

**Skipped:**
- `hashbrown` 0.14 → 0.16: The `raw` module was removed in 0.15 and
replaced with `HashTable` API. The codebase uses `RawTable`, `Bucket`,
`InsertSlot` extensively, requiring a non-trivial migration.
2026-03-03 09:49:20 +01:00
Luke Sandberg 8b70c61007 [turbopack] Don't schedule idle snapshots when we are in a short session (#90804)
In a few `next build` commands i saw multiple snapshots due to this.  Idle callbacks were firing near the end of the build.

```
  Creating an optimized production build ...
WARNING: The git repository is dirty, but File System Cache is still enabled. Manual removal of the filesystem cache database might be required.
✓ Finished writing to filesystem cache because: idle timeout in 6.1s
✓ Compiled successfully in 69s
✓ Collecting page data using 13 workers in 85ms
  Build ran with "compile" mode, to finalize the build run either "generate" or "generate-env" mode as well
✓ Finalizing page optimization in 32ms
Route (app)
...ommitted route table...
✓ Finished writing to filesystem cache because: stop in 3.5s
```

This is a bit wasteful since it is more efficient to write everything in a single batch at the end.

The solution is just to not schedule snapshotting operations in 'short sessions' (aka `next build`)
2026-03-02 23:58:52 -08:00
Josh Story 410c65808a Add static error when unstable_instant is used without cacheComponents (#90793)
`unstable_instant` requires `cacheComponents` to function — without it,
validation is entirely skipped and the config silently does nothing.
This adds a compile-time error in the SWC transform that catches the
misconfiguration early, matching the existing pattern used for segment
configs like `revalidate` that are incompatible with `cacheComponents`
(but inverted — `unstable_instant` requires it rather than being
incompatible with it). The error points directly to the export in the
user's source file.
2026-03-03 00:35:56 +00:00
Benjamin Woodruff c2a27526f8 Turbopack: Revert default loader runtime backend to child processes (#90675)
We're getting crashes in node: https://github.com/vercel/workflow/actions/runs/22500511618/job/65186125774#step:10:224

It looks like this should be fixed in newer versions of node:

- https://github.com/napi-rs/napi-rs/issues/2555#issuecomment-3645173223
- https://github.com/nodejs/node/pull/55877
- https://github.com/nodejs/node/pull/61400
- https://github.com/nodejs/node/pull/61661

So we may enable the worker threads feature by default if we detect a new enough version of node or bun.
2026-02-28 08:39:20 +01:00