Backports [Scope app-entry export validation to files inside the app
directory (#97350)](https://github.com/vercel/next.js/pull/97350) to
`next-16-3`.
Fixes#96859 on the 16.3 line. The regression came from #94962, which is
present in `next-16-3` (16.3.1) and absent from `next-16-2`, so 16.3 is
the only release branch that needs it.
Clean cherry-pick with no conflicts; every touched file is
byte-identical to the merged canary commit. `cargo test -p
next-custom-transforms --test errors react_server_components` passes on
this branch (33 fixtures). The end-to-end suite is left to CI.
<!-- NEXT_JS_LLM -->
Co-authored-by: Rodrigo Arias <rodrigo@arias.me>
Backports #97181 ("Allow literal exports in `'use cache'` files") to
`next-16-3`.
A `'use cache'` page or layout that exports a route segment config
literal, such as `export const instant = false`, `export const
dynamicParams = true`, `export const prefetch = 'partial'`, or `export
const maxDuration = 5`, fails the build with "Only async functions are
allowed to be exported in a "use cache" file".
Rejecting exported literals is meaningful only in a `'use server'` file,
where every export becomes a server reference and a runtime check
asserts that each one is a function. A `'use cache'` file wraps only
exports that are, or might be, functions, so known non-function values
can pass through. The check is now scoped to `in_action_file`.
### Verification on this branch
Cherry-picked from `c0c64a0ab1` with no conflicts. All source files are
byte-identical to canary. This touches `crates/next-custom-transforms`,
so the release build needs the native binary rebuilt. Behavioral
verification is left to CI on this branch; the change carries transform
fixtures on both the server and client graph.
<!-- NEXT_JS_LLM -->
Backports #96600 ([turbopack] Add `turbopack_ecmascript` and
`turbopack_wasm`'s embeded FS to `internal_assets_conditions`) to
`next-16-3`.
<!-- NEXT_JS_LLM -->
Co-authored-by: Sam Poder <sampoder@berkeley.edu>
Built using @sokra's Fleet! This re-arranges all of our chunking
experimental features under `experimental.turbopackChunking` and adds
the following options:
```javascript
/**
* Avoid creating more than one chunk smaller than this size, in bytes. Smaller
* chunks are merged into bigger ones to avoid that. Defaults to `50000` (50 KB).
*/
minChunkSize?: number
/**
* Avoid creating more than this number of chunks per chunk group. Chunks are
* merged into bigger ones to avoid that. Defaults to `40`.
*/
maxChunkCountPerGroup?: number
/**
* Never merge chunks bigger than this size, in bytes, with other chunks. This keeps code
* in big chunks from being duplicated across multiple chunks. Defaults to `200000` (200 KB).
*/
maxMergeChunkSize?: number
/**
* Minimum size, in bytes, for a component chunk to be emitted on its own when
* `generateComponentChunks` is enabled. Component chunks smaller than this are folded into a
* single remainder chunk. Defaults to `20000` (20 KB).
*/
minComponentChunkSize?: number
```
This lets next detect if it is in a worktree and find the main repo and
copy turbopack caches for build or dev to make initial build faster. I
wasn't sure about commiting tests for this and how exactly we'd won't to
go about this with the git requirement. I also could do some unit tests,
but following the library conventions there wasn't a nice abstracted out
file system. So I did various tests locally. If anyone has good ideas on
what kind of testing totally opened to it.
Also left a comment that I don't look in other worktrees but instead the
root project. Not sure if this is the right answer or not. But works for
the patterns I think people typically follow.
Currently, importing a new module causes a complete eviction (`clear()`)
and re-evaluation of server chunks, both in the Turbopack runtime's
module cache and Node's `require.cache`.
Here's what happens today:
- A new module is imported into a chunk's graph
- This changes its `availability_info`, which in dev for non-entry
chunks is encoded into the chunk's filepath
- `VersionedContentMap` works entirely on chunk paths. When we construct
instructions to transition a chunk into its new state, we fail to find
the prior state and fall back to `clear()` as described above.
An ideal version of this is a refactor that implements
`VersionedContentMap` on a per-module basis, not a per-chunk one. This
PR doesn't do that, but achieves consistent module-level updates when a
chunk's availability info changes.
It does this by implementing chunk lists for server entry chunks the
same way the client hmr implementation does: an entry chunk's version
aggregates the versions of its dependent chunks (including dynamically
imported ones), keyed by merger rather than path. Entry chunks do not
encode availability info in their paths, so they are insulated from
missing versions. Updates are applied in Node.js through the same shared
merged-update machinery in the unified hmr runtime that the client
already uses, plus a new `ChunkListUpdate` branch in the server hmr
client to unwrap the merged updates.
Details:
- The merged-update wire format (`EcmascriptMergedUpdate` etc.) moves
from turbopack-browser into shared `turbopack_ecmascript::chunk_list`,
along with runtime-agnostic `ChunkListVersion` and `update_chunk_list`
implementations.
- `turbopack-nodejs` gains a chunk content merger mirroring the browser
one. `EcmascriptBuildNodeEntryChunk`'s versioned content is now a
chunk-list content over its sync and async chunks. It is version-only
and never emitted; the entry chunk still inlines its loader calls.
- The aggregate server hmr subscription now tracks only entry chunks.
Shared chunks under `server/chunks/` ride the entry's `ChunkListUpdate`
as module deltas, so their content-hash paths no longer matter.
- The Node hmr client applies `ChunkListUpdate` by feeding each nested
merged update through the shared apply path. Modules that appear in an
"added" chunk but already exist in the module cache were moved by a
chunk rename and are treated as modified.
- On a successful partial apply, the hot reloader clears the manifest
cache for updated chunks and notifies browsers to refetch RSC, without
clearing `require.cache`.
### Test Plan
Adds a series of e2e tests to catch the described manual test plan in
both entry and dynamic chunks.
This moves modules out of `turbopack-browser` and into
`turbopack-ecmascript` so it can be used in the server hmr
implementation as well.
Move the chunk-list version, update, and merged-update wire types out of
`turbopack-browser` into a shared `turbopack-ecmascript::chunk_list`
module so both the browser and node chunking contexts can build on them.
Rename `EcmascriptDevChunkListVersion` to `ChunkListVersion` and make
`update_chunk_list` runtime-agnostic (plain path->`VersionedContent`
maps).
Pure refactor: no behavior change.
<!-- NEXT_JS_LLM_PR -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
This replaces per-chunk Server HMR turbo tasks with a single firehose
subscription that diffs every HMR chunk. This significantly cuts the
number of tokio task churn on projects with many server chunks and
centralizes the diff/clear logic. It leads to a multi-second saving in
both cold and warm builds in a large app.
This PR also renames `clear()` to `reEvaluateAllModulesExpensive()` to
label directly that this is a costly operation and should only be done
in exceptional circumstances.
A following PR will bring this to client chunks.
Rust:
- New `aggregate_hmr` module: `AggregateHmrVersion` keyed by chunk path,
`merged_partial_update` builder, and `is_hmr_eligible_chunk` (excludes
`.map` files, which would force every diff to `Total`).
- `Project::all_hmr_version_state` / `all_hmr_update` aggregate over the
whole `hmr_root_path`. The seed transition emits an empty `Partial` so
the JS consumer doesn't treat it as a restart and wipe handlers the
triggering request just populated. Any chunk requiring `Total`/`Missing`
escalates the batch.
- `VersionedContentMap::hmr_chunks_in_path` lists eligible chunks with
their `VersionedContent`.
NAPI:
- `projectAllHmrEvents(target)` returns a single subscription.
JS:
- `setupServerHmr` subscribes once via `allHmrEvents` instead of fanning
out over `hmrChunkNamesSubscribe`.
- `reEvaluateAllModulesExpensive()` evicts every chunk under
`server/chunks/` from `require.cache` directly rather than tracking
subscriptions. This is a bit fragile as it relies on the path prefix and
scanning require.cache.
The previous `@mswjs/interceptors` has a bug where the response it
forwarded never set the [`complete`
field](https://nodejs.org/api/http.html#messagecomplete). This wasn't an
issue so far (or not reported) but is an issue for `httpxy` which relies
on an accurate `complete` field.
Bumping `@mswjs/interceptor` now so that we can switch from `http-proxy`
to `httpxy`.
## Summary
Add Vite-compatible `caseSensitive` support to `import.meta.glob()`.
Matching remains case-sensitive by default, while `caseSensitive: false`
applies case-insensitive matching to directory traversal, filenames,
positive patterns, and negative patterns.
The option is included in matcher equality and non-default virtual
module identity, and is exposed through diagnostics, TypeScript
declarations, and the Turbopack options reference.
## Verification
- `cargo test -p turbo-tasks-fs glob`
- `cargo nextest run -p turbopack-tests -E "test(import_meta_glob) and
not test(import_meta_glob_errors)"`
- `cargo fmt --all -- --check`
- `pnpm build-all` and `pnpm --filter=next types` are blocked by
current-canary `sharp.default` type errors in
`packages/next/src/server/image-optimizer.ts`
<!-- NEXT_JS_LLM -->
In a project whose absolute path contains characters that percent-encode
in URLs — e.g. a space — the dev overlay showed no code frame and raw
`file://` URLs instead of source locations for server frames.
Two encoding mismatches caused this:
- Turbopack's `trace_source` percent-decoded the source map's original
file URL before comparing it against the still-encoded project root URI,
so the containment check failed ("Original file ... outside project")
for any project path with such characters. The comparisons now stay in
the encoded domain and only the outputs are decoded back into paths.
- React synthesizes stack frame `file:` URLs by prepending the scheme to
a filesystem path, so the overlay receives URLs with e.g. raw spaces.
These aren't well-formed, and they don't match the `url.pathToFileURL`
form Node.js keys its source map cache by. The overlay middleware now
re-encodes them through WHATWG URL parsing, which tolerates such input.
## Test Plan
New tests.
Known cases that remain broken are commented out with a TODO.
These could be resolved if https://github.com/react/react/pull/37105 or
equivalent lands.
## Before
<img width="825" height="732" alt="Screenshot 2026-07-25 at 14 39 18"
src="https://github.com/user-attachments/assets/0f119e52-ec86-495a-bf5c-5339faa1d606"
/>
## After
<img width="1126" height="733" alt="Screenshot 2026-07-25 at 14 58 14"
src="https://github.com/user-attachments/assets/a263ee8a-4cfe-4358-9a8e-2e58b4b16aa5"
/>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
### What?
Bound the compile-time Google Fonts fetch on the Turbopack path so `next
dev` / `next build` can't hang forever on a stuck network.
### Why?
`next/font/google` fetches from Google Fonts at compile time. When the
connection *hangs* (captive portal, packet-dropping proxy, broken IPv6),
the fetch had no timeout, so compilation blocked until the OS connect
timeout (~75s) or indefinitely.
### How?
- `FetchClientConfig` gets a `connect_timeout` (10s) and total `timeout`
(60s); Google Fonts overrides them to 5s / 30s.
- Transient failures retry up to 3×, each attempt its own
`duration_span!`.
- On failure: `next build` errors (fails the build), `next dev` warns
and uses the fallback font. Both messages report the attempt count and
suggest `next/font/local`.
### Follow-ups
- [ ] Reduce the dev-mode timeout (build keeps the longer value).
- [ ] Port the fix to the webpack/JS path (`packages/font/src/google`).
Related: #92301. #76473.
---------
Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
The `rewriteTurbopackSources` function has been mostly dead since #85146
this closes the remaining gap with the edge runtime in dev (an
accidental omission) and drops the js side munging
Noticed this bug where we were assuming chunks were plain strings, which
may not always be the case given #95261. When putting together
https://github.com/react/react/pull/37095, I noticed this was also a
spot we were missing so this fixes that.
Almost all of this diff is a regression test.
This reduces peak memory consumption with the original repro case in
[vercel/next.js#81161](https://github.com/vercel/next.js/issues/81161).
If you walk the 15 routes in a quick succession and capture vmmap
--summary at the end of each fast sweep (before memory is released),
with this change the number reliably goes down from 3 GB to 2.6 GB:
```
before 1 3.0G 17
after 1 2.6G 16
before 2 3.0G 16
after 2 2.6G 16
before 3 3.1G 18
after 3 2.6G 17
before 4 3.1G 17
after 4 2.6G 16
before 5 3.1G 17
after 5 2.6G 17
before 6 3.0G 17
after 6 2.5G 17
before 7 3.1G 17
after 7 2.6G 17
before 8 3.0G 25
after 8 2.6G 24
before: [3.0, 3.0, 3.1, 3.1, 3.1, 3.0, 3.1, 3.0]
after: [2.6, 2.6, 2.6, 2.6, 2.6, 2.5, 2.6, 2.6]
mean before=3.050G after=2.587G diff=0.462G (-15.2%)
exact permutation test: p = 2/12870 = 0.00016
```
More runs on this app:
<img width="938" height="623" alt="Screenshot 2026-07-22 at 20 59 12"
src="https://github.com/user-attachments/assets/01c41656-eb5a-41f9-8871-b4ce262c54d5"
/>
And here's an internal app showing ~20% dev memory win at peak (although
reclaiming memory settles it):
<img width="832" height="402" alt="Screenshot 2026-07-22 at 16 45 34"
src="https://github.com/user-attachments/assets/8c39f668-d431-43f9-b533-d39dfaa97c15"
/>
---
<details>
Module source maps are serialized to a JSON rope right after code
generation and treated as opaque bytes from then on. Since
`sourcesContent` — the full original source text — is inlined into that
JSON, every later step that modifies the map has to copy all of it:
rewriting the `sources` URLs for each chunking context copies the entire
map per context, and embedding the module map into a chunk's sectioned
map copies it again, once per layer that includes the module. In a dev
session on a dependency-heavy app, each module's source text ends up
resident ~5 times over, and source maps end up dominating the memory the
task store holds for codegen output (in one measured app, maps were 57%
of module factory bytes, and `sourcesContent` alone was 36%).
This PR stops copying the text. `StructuredSourceMap` keeps a map in
field form until the moment it is actually emitted:
- Fields we never touch (`mappings`, `names`, `version`, ...) are stored
as verbatim raw JSON snippets, so producers' bytes round-trip untouched
and re-emission is rope sharing rather than re-serialization.
- `sources` is the one field rewrites modify, so it is stored typed, and
the per-chunking-context "fileify" rewrites become a plain string
rewrite of that field instead of a parse–modify–reserialize of the whole
map.
- `sourcesContent` entries are individual, already-JSON-escaped ropes.
Rewritten maps and chunk maps share them; the source text is escaped
once per module and never copied again.
Two construction paths, chosen by where the bytes come from: maps we
generate ourselves are built directly from the `swc_sourcemap` object
(`from_swc_map` takes the contents out, serializes the small remainder,
and never round-trips source text through JSON), while maps that arrive
as bytes (prebuilt maps next to external code, the
single-complete-original fast path) go through `from_json`. The emitter
writes fields in exactly `swc_sourcemap`'s order and format, pinned by
golden tests that assert byte-identity with `to_writer` — so emitted
chunks and maps are byte-for-byte unchanged, and no snapshot fixtures
change. The CSS pipeline gets the same treatment; the single-file output
path is left alone since it serializes straight into the final artifact
and there is nothing to share.
The win is proportional to how much duplicated source-map text an app
holds, so results range accordingly. On a dependency-heavy reproduction
app (13 routes sharing large wallet SDKs; rapid route sweep in `next
dev`, macOS physical footprint, 8 interleaved cold runs per side): 3.05
GB → 2.59 GB mean (−15%), with zero overlap between the two
distributions (exact permutation test p ≈ 0.00016) and per-route
latencies unchanged. On `bench/basic-app` (small first-party modules): a
full sweep of all 42 routes drops the post-sweep footprint 3.4–3.5 GB →
2.4–2.5 GB (−29%, interleaved runs); cold compile −4%, idle footprint
identical (eviction reclaims both sides equally at idle). On
`bench/heavy-npm-deps` (heavy deps disabled in its default config) and
on very large first-party apps whose memory is dominated by the JS heap
and application state rather than map text, the change measures flat —
as expected, since there is little duplicated text for sharing to
recover. `small_apps` build benchmarks are flat except `date-fns-all` at
+1–2% (~2.5 ms): that app is 1000+ tiny modules and `from_swc_map` adds
one small-skeleton JSON parse per module, with no redundancy to win back
in a single-context one-shot build. The single-complete-original fast
path (per prebuilt module with a shipped map, and per minified chunk) is
cost-neutral: the structured map is built by splitting the raw map's
serde fields directly (`from_serialize`), so the serialized document is
never materialized or re-parsed — 93 ms per 5 iterations on a synthetic
21.7 MB map, matching the pre-PR baseline exactly, with byte-identical
output pinned by a property test. Serialized task-store size is
unchanged (bincode expands sharing back out), so this is purely a
live-memory change.
Verified: snapshot suite passes with zero fixture changes, execution
suite matches canary (the single pre-existing failure reproduces on the
base tag), unit suites pass including new golden byte-identity tests,
and emitted `.map` files were validated end-to-end in a running dev
server (multi-section chunk maps, fileified `file://` sources,
`sourcesContent` present and byte-identical between base and this
branch).
</details>
I was seeing failures on https://github.com/vercel/next.js/pull/95628
and went down this rabbit hole.
- We were (incorrectly) not canonicalizing the `DiskFileSystem` root
dir. This happened to work out for us because pnpm also wasn't
canonicalizing junction point targets, and so the root dir prefix
matched. However after `#95628`, pnpm started canonicalizing junction
point targets because we added `pnpm-workspace.yaml` files. This was
just coincidence and indicative of much deeper problems.
- `dunce` isn't a good fit for us. It picks a win32 or verbatim
representation based on path length, which would mean that long paths
inside of a short root path would never work because we'd never be able
to strip the prefix, since we'd be looking for a win32 path prefix.
Omnipath is better for what we want to do:
https://docs.rs/omnipath/latest/omnipath/windows/trait.WinPathExt.html
- The windows verbatim path format works with long paths (>260
characters) and behaves a lot closer to cross-platform unix paths, which
is what we want. E.g. if somebody tries to import a JS module from a
Windows 8.3 short path, it should fail. This will fail with verbatim
paths (good!). This PR *always* uses the verbatim path format internally
within `DiskFileSystem`.
- `read_link` was re-inventing `try_from_sys_path` but badly. Just call
`try_from_sys_path`.
- `turbopack/crates/turbo-tasks-fs/src/embed/file.rs` was dead code.
- `validate_path_length_inner` was incorrectly referring to verbatim
paths as UNC paths. These are two different things.
- Canonicalize any absolute symlink targets outside of the
`DiskFileSystem` root path, resolving any symlinks they may have,
normalizing case-insensitive paths, and handling Windows 8.3 short name
format.
## Some Context About Windows Path Formats:
- win32 paths: These are your normal user-friendly `C:\foo\blah` paths.
When calling most filesystem APIs, these are limited to 260 characters
unless the user has enabled long paths on the system, and avoiding the
limit with these paths also requires a [`longPathAware` application
manifest](https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#longPathAware).
You can mix `/` and `\` in win32 paths, and Windows will normalize the
path separator for you.
- verbatim paths: This is a rust-specific name (used by the stdlib) for
the "extended length" prefixed paths. These are paths starting with
`\\?\`. These paths can be up to ~32,767 characters long. They do not
support Windows 8.3 shortnames. They do not normalize between `/` and
`\`, and do not support `.` or `..` components, however
[`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join)
will do this normalization if given a verbatim path.
- Windows 8.3 shortnames: A legacy format where paths can be represented
by 8 characters, followed by a tilde and a digit (or hash in some
extreme cases where there are many collisions), followed by a 3
character file extension. NTFS supports this,
[ReFS](https://learn.microsoft.com/en-us/windows-server/storage/refs/refs-overview)
(used by [Windows Dev
Drive](https://learn.microsoft.com/en-us/windows/dev-drive/)) does not.
- UNC ("Universal Naming Convention"): This is a specific representation
of paths used for remote files on network servers.
- `file://` URIs: These must be formed from [win32 style
paths](https://docs.rs/url/latest/url/struct.Url.html#method.from_file_path),
though the normal 260 character limit typically does not apply here.
- canonicalize: Corresponds to
[`std::fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html).
This involves actual fs operations (that we can't easily track), and
recursively resolves symlinks. It will fail if the file path does not
exist. On windows, this always returns a verbatim path.
- wide format: Windows paths are utf-16. Generally we don't support
paths that can't be encoded to utf-8, but we should try to fail as
obviously as we can in these cases. We don't have good filesystem issue
handling yet though.
- case sensitivity: Generally paths are not case-sensitive on Windows or
macOS, but are case-sensitive on most Linux filesystems. The exact
definition of how case sensitivity works with unicode paths is highly
platform and filesystem dependent.
- junction points: The windows equivalent of symlinks (symlinks are also
supported, but require developer mode to be turned on). These can
contain an arbitrary path reference in any format (win32, verbatim, etc)
with any case and with windows 8.3 short names. Junction points are
always target absolute paths to directories.
This has been causing a lot of confusion when contributors have been
debugging tree shaking, eg.
https://github.com/vercel/next.js/issues/95698 and the linked issues.
Turbopack's standard tree-shaking features are controlled through the
`turbopackRemoveUnusedExports` and `turbopackRemoveUnusedImports`
options which default to true.
This PR introduces two new things: a pass of the analyzer that
recognises exports from CJS modules (in the form of `exports.foo`, see
`analyze_cjs_exports`) and then a CodeGen that reads `export_usage_info`
to remove CJS exports that are not used.
This PR does not add anything additionally to `export_usage_info`, so
this means that we only trim from modules that are imported with `import
{ foo } from "bar"`. The CodeGen depends on the output of
`analyze_cjs_exports` for the locations of exports, whether it is safe
to drop exports (its pretty conservative), and also whether `__esModule`
is present.
This PR builds on top of the other PRs in this stack, it is the PR that
switches to inlining the bootstrap code and dropping the runtime file
that each route had.
We used to generate the runtime file for each `ChunkGroupResult` in
`evaluated_chunk_group()`, I've added `chunk_group_bootstrap_params`
which are the parameters that were unique to each chunk group. These are
inlined into each page's HTML in
https://github.com/vercel/next.js/pull/94666. They look like:
```javascript
{"otherChunks":["output/0_9x_turbopack-tests_tests_snapshot_basic-tree-shake_export-namespace_input_1mqgz2-._.js"],
"runtimeModuleIds":["[project]/turbopack/crates/turbopack-tests/tests/snapshot/basic-tree-shake/export-namespace/input/index.js [test] (ecmascript)"]}
```
Whether or not the inline-able code is generated is based on the
`inline_chunk_group_bootstrap` which is still false in all cases at the
moment but will be turned to true in
https://github.com/vercel/next.js/pull/94666.
#95815 refactored the NFT flow and ended up only tracing the RSC
template and it's subgraph. Not the server actions loader module whose
module graph is completely separate.
---
Clauded.
Attempts to fix
https://github.com/vercel/next.js/discussions/95130#discussioncomment-17652155.
---
<details>
## Summary
Fixes the NFT tracing regression behind #95815 and the deploy failures
reported in
https://github.com/vercel/next.js/issues/87737#issuecomment-4897366831.
Since the 16.3 NFT rewrite (#94224 / #92901), a `serverExternalPackages`
entry that is imported **only from a `'use server'` action referenced by
a client component** ends up in the route's trace as the content-hashed
alias symlink (`.next/node_modules/<pkg>-<hash>`) **without the store
files the symlink points to**. Any output assembled from the traces
(Vercel lambdas, `output: 'standalone'`) then contains a dangling
symlink, and invoking the action throws `Failed to load external module
<pkg>-<hash>` (HTTP 500). 16.2.9 traces both. Both CJS and ESM externals
are affected, with any package manager — the ESM/bun/monorepo framing in
the issue reports was a coincidence of which packages were only
reachable through actions.
The cause: `AppEndpoint::trace_result` started the trace DFS only from
`rsc_entry`, but the server actions loader is a separate module-graph
entry (`additional_entries`). It is chunked into the endpoint output
(which emits the alias symlink via the chunk's output assets), yet its
subgraph — including the externals' traced target files — was never
visited by the tracer. `trace_endpoint` now accepts multiple entry
modules and the app endpoint passes the actions loader alongside
`rsc_entry`.
The test observes the user-visible failure: it builds with `output:
'standalone'`, deletes everything except the standalone output (so only
traced files are available, like a deployed lambda), runs `server.js`,
and invokes the server action in a browser. Without the fix the action
fails with `Failed to load external module lodash-<hash>`; on 16.2.9 the
same fixture responds with the action result. Note the fixture must not
import the externals from any other route: the standalone output is the
union of all route traces, so a route that traces them correctly would
mask the missing entries.
## Verification
- `pnpm test-start-turbo
test/production/standalone-mode/server-action-externals/standalone-mode-server-action-externals.test.ts`
— fails without the fix (`Failed to load external module
lodash-fb78ad0536ffcdab` from the standalone server), passes with it.
The equivalent standalone flow on `next@16.2.9` returns the action
result with HTTP 200.
- `pnpm test-start-turbo
test/e2e/externals-transitive/externals-transitive.test.ts`
- `pnpm test-start-turbo
test/production/standalone-mode/server-actions/standalone-mode-server-actions.test.ts`
<!-- NEXT_JS_LLM -->
</details>
Extracted from https://github.com/vercel/next.js/pull/95468 and arguably
the main fix there.
We need to add code to wait for top-level awaits in two cases:
- If a metadata module (like `opengraph-image.tsx`) is slow to evaluate,
cache warming wouldn't "wait" for it, and then the actual build would
fail with `Unexpected cache miss after cache warming phase`. This is
fixed with placing `instrumentModuleGetter` around them.
- If the evaluation is fast but still async (e.g. `await readFile` on a
large font), the build passes but the segment collector reads the module
too early, doesn't see `generateStaticParams`, and so the image becomes
dynamic instead of prerendered. The fix is `await ensureUserland` before
looking at exports.
Remaining callsites are wrapped so we don't add more bugs like this, but
it's not strictly required now because they are already being awaited
elsewhere. I've done this for consistency.
New tests fail before the fix.
Fixes#95125 in two ways
The OP admits that they misread the message in a few ways, but we can
fix all of them
1. Be specific about what function might need an annotation in the error
message. A user with a nearby `path.join` might think that that is where
the annotation belongs instead of on the function with the warning.
2. Be more flexible with how these `ignore` comments are interpreted
`fs.readFileSync(path.join(process.cwd(), someVar))` shouldn't require 2
annotations. Handle this by checking for and detecting `turbopackIgnore`
comments in all expressions! This will then bubble up through the linker
and just cause us to ignore corresponding fs access apis.
## Summary
The graph-based CSS chunker's `linearize` step produces the global
module order that `split_into_chunks` then cuts into chunks. It
previously broke ties between ready modules using a weight-sorted stack.
It now prefers the ready module that shares the most chunk groups with
the previously placed module, so modules that are loaded together end up
adjacent in the global order and split into better-aligned chunks. On a
real world example this measurably lowers the modeled chunk-loading
cost, with fewer requests and less over-fetched CSS.
The shared-group count is read from edge weights (how many chunk groups
two modules co-occur in). Since `make_acyclic` deletes edges to break
cycles, it now returns the edges it cut so `linearize` can still count
those conflicting-order co-occurrences — reconstructing the full
co-occurrence without cloning the graph.
## Verification
- `cargo test -p turbopack-core style_groups_graph::tests` (all pass)
- Validated on a real world example that the resulting global order is
deterministic and the modeled chunk-loading cost decreases.
<!-- NEXT_JS_LLM_PR -->
Ensures the module graph is not bundled. Right now this has no effect
since all the modules imported by `navigation-test-lock` have legit uses
in prod builds.
Also nice to not have to fork every single implementation inline.
### What?
Adds a new `'auto'` mode to the experimental `turbopackMemoryEviction`
config option and makes it the default. In `'auto'` mode, Turbopack only
evicts in-memory cache after a snapshot once enough memory has been
allocated since the last eviction to make the work worthwhile.
The option now accepts three values:
- `false`: never evict.
- `'auto'` (new default): evict after a snapshot only once a memory
threshold has been crossed.
- `'full'`: evict all evictable data after every snapshot (the previous
behavior).
### Why?
When the persistent (FileSystem) cache is enabled, Turbopack snapshots
its in-memory state to disk and can then evict those in-memory copies to
reclaim memory, reloading them from disk on demand.
Previously, with eviction enabled (`'full'`), we evicted after *every*
snapshot. This is too aggressive: tasks get restored from disk and then
immediately evicted again, cycle after cycle, wasting work for little
memory benefit.
`'auto'` mirrors the existing persistence-threshold model (we already
skip a snapshot when too little compilation time has accumulated to
justify its cost). Here the proxy is memory instead of time: it isn't
worth paying the restore-then-re-evict churn to reclaim a small amount
of memory.
This was motivated by an example in v0.app where the client would poll
the server every 5 seconds leading to a pathological behavior
* client poll ->
* next.js ensurePage ->
* turbopack writeEndpointToDisk
* recompute and restore settings for that endpoint (3-10ms of io work)
* realize there is nothing to do
* respond to client
* 2 seconds later..... persist and evict everything saving 0.5M of ram
(100ms of work! writes out 2 SSTs and then compacts them!)
* 3 seconds after that restart the loop
This is silly, #95137 will prevent the persistence loop from occurring ,
this PR will also just skip the 'restore and recompute' work using a
similar strategy.
### How?
We can't measure exactly how much memory a sweep would reclaim, so we
use `TurboMalloc::memory_usage()` (process-global net live bytes) as a
proxy. In `'auto'` mode an eviction sweep runs only once the net bytes
allocated since the last eviction exceed a threshold (default 128 MiB,
overridable via `TURBO_ENGINE_EVICT_MIN_BYTES`). The first eviction
after startup always runs. The threshold scales down under OS memory
pressure (`TurboMalloc::memory_pressure()`) so we evict more eagerly
when memory is tight.
**Backend (`turbo-tasks-backend`):**
- `BackendOptions.evict_after_snapshot: bool` → `eviction_mode:
EvictionMode` (`Off` / `Full` / `Auto`).
- New `EvictionControl` type owns the policy: the mode plus the
threshold bookkeeping. The background snapshot loop calls
`should_evict(snapshot_had_new_data)` once per cycle and
`record_eviction()` after a sweep, so the loop no longer branches on the
mode. `'full'` and `false` behave exactly as before.
<!-- NEXT_JS_LLM_PR -->
I'm introducing a new endpoint that doesn't compile SSR when doing a
page navigation, I'd like to call it a `rscEndpoint` but currently this
name is taken. `rscEndpoint` appears to be used for detecting changes
for HMR so I've renamed it to `rscHmrEndpoint` so I can use that name
for the new `rscEndpoint`.
Thus far, it wasn't possible to use `require()` in a template file with
Webpack.
But I needed to add a `if(NEXT_RUNTIME !== "edge") { const x =
require("../../something"); }`
Turbopack doesn't do any regex rewrite hacks, so this just worked
Instead of hardcoding the list, we now automatically detect any
`.browser` sibling and create a compiler alias for that module.
Together with a linting rule, we'll ensure no future regressions are
handled automatically (detection of conditional require and missing
.browser sibling).
This isn't the smoothest DX yet. Ideally we'd generate the list at
Next.js compile time (i.e. before publishing) to avoid having to rerun a
script (I'll follow-up). Generating the list during build time (next dev
or next build) is just another tiny cost users would have to pay.
### What?
When profiling is enabled (`--experimental-cpu-prof` /
`--internal-trace`, or the `NEXT_CPU_PROF` / `NEXT_TURBOPACK_TRACING`
env vars), Next.js writes a `.gitignore` containing `*` into
`.next-profiles/` as it creates the directory. This keeps the profiling
output out of git and out of the way of tools that scan the project
tree.
### Why?
`.next-profiles` is a sibling of `.next`, so it's intentionally *not*
covered by the `.gitignore` create-next-app ships. The motivation was
that profiles wouldn't bloat `.next` and wouldn't be silently ignored.
But a sibling dir full of large profile/trace files turns out to be a
liability:
- **Ecosystem tools scan it.** Tailwind v4 aggressively auto-scans files
and has opened a `trace-turbopack.bin` and crashed; Turborepo <2.8.13
hashes the file and OOMs, and >=2.8.13 fails to start `dev` watchers
because reading the large trace is too slow. (workflow watchers are
likely affected too.)
- **Turbopack NFT globbing** can read/hash the file via overly broad
`fs.read` patterns.
- **Accidental commits.** Pushing a multi-GB trace would be miserable
for a customer helping us debug.
These have each gotten point bandaids (#94587, #94570), but the real fix
preserves the original motivation: a nested `.gitignore` with `*` keeps
the files off git **and** makes gitignore-respecting tools (Tailwind,
Turborepo) skip the whole subtree, which resolves the scanning failures.
Reverting to `.next/profiles` was the alternative; the gitignore
approach keeps the existing path.
### How?
- `ensureProfilesDir()` (`packages/next/src/lib/profiles-dir.ts`)
creates `.next-profiles` and writes the `.gitignore` (only if absent, so
user edits survive). Single source of truth.
- `bin/next.ts` calls a `setupProfilesDir()` helper on build/dev/start,
gated on the env vars so both the CLI flags and direct env-var usage are
covered. Skipped when `NEXT_TURBOPACK_TRACING_PATH` redirects trace
output.
- Rust keeps `create_dir_all` as a backstop for the trace dir; the
`.gitignore` is written in JS only.
- Docs switched from `NEXT_TURBOPACK_TRACING=1` to `next dev
--internal-trace`, and the stale `trace-turbopack` filename corrected to
`trace-turbopack.bin`.
<!-- NEXT_JS_LLM_PR -->
This computes the implementation hash of a given `use cache`:
- it includes the module containing the `use cache` function, plus all transitive imports of that module (including externals via NFT)
- what does it hash
- for bundled code, it hashes the generated code (so including any AST transforms, inlined env vars, etc)
- for NFTd files, it hashes the file content on disk
- So this is basically equivalent to hashing the generated JS chunks, except that it's scoped to only what the given actions module uses
- (so notably, if you have multiple use cache functions in a given file, then they will all have the same hash)
<br/>
- [x] Gate behind experimental flag
- [x] Only compute hash for `use cache` functions, not all server actions
- [ ] `final_read_hint` is a problem. Some chunk items are now codgened twice, so it recomputes the AST
- [x] disable for dev for now
- [ ] Measure perf impact
```jsonc
// .next/server/server-reference-manifest.json
{
"node": {
"806f4954cfbb75404a19d6d405065ed9059cc0cab2": {
"workers": {
"app/rsc/page": {
"moduleId": "[project]/bench/app-router-server/.next-internal/server/app/rsc/page/actions.js { ACTIONS_MODULE0 => \"[project]/bench/app-router-server/app/rsc/logic.js [app-rsc] (ecmascript)\" } [app-rsc] (server actions loader, ecmascript)",
"async": false,
"exportedName": "$$RSC_SERVER_CACHE_0",
"filename": "bench/app-router-server/app/rsc/logic.js",
"codeHash": "413e394d9597b35df12828a6a7fd6363" // <------------
}
},
"filename": "bench/app-router-server/app/rsc/logic.js",
"exportedName": "$$RSC_SERVER_CACHE_0"
}
},
"edge": {},
"encryptionKey": "jOF2TbpNLjp2oOhl3VPBwGE7luodnei9clJPq/gaYQo="
}
```