Commit Graph

35739 Commits

Author SHA1 Message Date
Josh Story 50073d6def Suspend unresolved root parameter reads inside public caches
A generic fallback shell can have an unresolved root parameter. Reading
that root directly already suspends, but reading it inside `use cache`
returned the opaque placeholder and could persist placeholder-derived UI
in the resume cache. For example, a cached `lang().toUpperCase()` could
render `%%DRP:LANG:...%%` for a later French request.

Propagate the fallback mask into public caches and abort the cache fill
when an unresolved root value is consumed. Share cancellation through
nested caches so an outer cache cannot retain the inner placeholder read.
Preserve tracked root dependencies while cancellation completes.

Add a real Cache Components fixture without paramMatching. Cold fallback
debug renders exercise direct and nested cached reads, followed by concrete
requests that must not reuse placeholder-derived content. All three tests
fail without this fix. Existing concrete-root cache coverage also passes.
2026-09-19 11:02:53 -07:00
Josh Story ee28e1a796 Name the build-time generator work-unit context independently of gSP
Rename GenerateStaticParamsStore to BuildTimeGeneratorStore and its internal
discriminant from generate-static-params to build-time-generator. Update every
exhaustive consumer without changing its behavior, fields, or error messages.

generateStaticParams remains the only producer in this commit. Naming the
context for its capabilities lets other build-time generators reuse the same
cache/request restrictions rather than adding a function-specific store and
duplicating every switch. Function-specific contexts can be introduced later
if their actual requirements diverge.

Verified that all 37 changed files differ only by the two renames. The full
build/type checks pass, as do the five existing gSP root-parameter success/error
tests and the existing static-HTML assertion that exercises cached gSP helpers.
2026-09-19 11:02:52 -07:00
Josh Story bfabf995db Preserve array values for catch-all fallback parameters
Keep the public parameter shape consistent while rendering a fallback shell:
both [...slug] and [[...slug]] use an array containing the opaque placeholder,
rather than changing from an array to a scalar string when the value is unknown.
For example, code receiving params.slug must not see a different value shape
solely because the build is producing a less-specific shell.

The router tree still uses the same serialized segment string. Ordinary dynamic
parameters are unchanged. This fix is independent of experimental matching.

Update the two existing fallback-shape assertions: both fail before the fix;
all 47 getDynamicParam tests and both snapshots pass afterward.
2026-09-19 11:02:51 -07:00
Josh Story 59ad3de654 Preserve closed-parameter restrictions in client route prediction (#98889)
## Summary

Preserve the restrictions of `dynamicParams = false` in client route
prediction, independently of the proposed parameter-matching API.

For `/products/[slug]` with only `allowed` generated during the build,
learning `/products/allowed` must not imply that `/products/rejected`
exists. This remains true when the page never reads `slug`. The
transport-tree builder now attaches `PrefetchHint.IsClosedParam` to the
affected dynamic node, rather than putting a route-level bit on the
response root or copying it to every prefetch node. Live rendering,
error trees, and prefetch-hint collection preserve that placement.

Legacy `dynamicParams = false` closes the entire parameter tuple. For
`/catalog/[lang]/products/[slug]/details`, both `[lang]` and `[slug]`
receive the hint; the static segments do not. Shared ancestors refresh
their hints even when their UI is reused: navigating between open and
closed pages under the same `[slug]` must add or clear the restriction.

Client behavior remains conservative: optimistic route prediction
declines a tree containing any closed parameter and asks the server to
resolve the destination. Existing apps combining `dynamicParams = false`
with `experimental.optimisticRouting` therefore lose prediction for
those routes. Sending allowed parameter values to the client and
predicting valid destinations from that list is future work, not part of
this change.

This PR is an independent prerequisite extracted from #97393. It does
not enable Cache Components, introduce parameter-matching configuration,
or change which parameter values are allowed. The later API layer
supplies exact closed-parameter names for routes with a closed prefix
and an open suffix.

## Verification

- The legacy routing/navigation fixture runs with Cache Components
disabled. Revised node-placement assertions fail against the old
response-root representation. The shared-sibling navigation test also
reproduced stale hints before the skipped-ancestor refresh was added.
- Initial documents and live client navigations with `prefetch={false}`
place the hint on `[slug]`; open routes carry no closure hint. A nested
legacy route marks both dynamic parameters, and open/closed sibling
navigation updates a reused ancestor.
- Five tests pass in development with both Turbopack and Webpack. The
three existing prefetch-dependent scenarios retain their production-only
gates; all eight tests pass in production with both bundlers.
- The seven production scenarios present before adding shared-sibling
navigation also passed with optimistic routing disabled in the Webpack
axis-A configuration.
- Existing config and transport-tree helper unit suites pass (22 tests
and one snapshot), and repository TypeScript checks pass.
- The eight-test legacy fixture also passes on the integrated API head.

An exploratory dev run of the combined routing-404 scenario encountered
an intermittent Turbopack `NoFallbackError`/500; a subsequent run
returned the expected 404. This PR does not fix that observation or
claim dev coverage for that production-only scenario.

<!-- NEXT_JS_LLM -->
2026-09-19 11:02:46 -07:00
Hendrik Liebau 75b9f201be [test] Fix deployment tests that relied on implicit startup (#98935)
PR #98776 moved deployment creation into `start()` and made deploy mode
honor `skipStart`. Two suites consequently requested an undefined URL,
as reported in
https://github.com/vercel/next.js/actions/runs/35407031061.

The `use-cache-output-export` suite now skips automatic startup only in
local production mode. The `trace-build-file` suite now excludes deploy
mode because its trace assertions require local build output.
2026-09-19 13:32:08 +00:00
Benjamin Woodruff ‮ 7b58e5880c Turbopack: Add support for specifying additional roots (#98003)
Full motivation and plan here:
https://app.notion.com/p/vercel/Turbopack-pnpm-Global-Virtual-Store-383e06b059c480579403ddfd71cc2d40?source=copy_link

The goal is to allow `DiskFileSystem` to traverse outside of it's own
root to other configured `DiskFileSystem`s when following symlinks. We
may allow traversal in other situations in the future, but this is
limited to symlink resolution for now.

## Global Virtual Store

The motivation for this is to enable [pnpm's Global Virtual Store
feature](https://pnpm.io/global-virtual-store) (and there are other
package managers doing this, including nub and bun).

We'd expose the ability to manually configure this in `next.config.js`,
but we should also auto-configure ourselves for popular package managers
(or at least make a best effort to do so, the `PNPM_HOME` semantics can
be complicated). The `ignoreIfMissing` option is provided for this
situation: We can configure a bunch of roots automatically, and they
only actually get set up if they exist, the check for directory
existence is cheap.

## NFT changes

This requires a couple extensions to the `*.nft.json` file format:
https://github.com/vercel/next.js/pull/98469

## Related Issues

- #93556
- https://github.com/pnpm/pnpm/issues/14972
2026-09-18 17:08:40 -07:00
Benjamin Woodruff ‮ 469a7c5573 Turbopack: Add symlinks and additional roots to NFT metadata (#98469)
Implements the following extension:

```typescript
export interface NftFileList {
  // File paths relative to the directory containing the `.nft.json` file, or
  // the current root (if inside of `NftAdditionalRoot`).
  //
  // When using webpack, these paths may exist outside of the tracing root. The
  // [`@vercel/next` package ignores these paths][vc-next].
  //
  // When using Turbopack, these paths are all guaranteed to exist within the
  // `turbopack.root` specified or inferred from the `next.config.js` file.
  //
  // [vc-next]: https://github.com/vercel/vercel/blob/%40vercel/next%404.20.5/packages/next/src/server-build.ts#L1022-L1026
  files: string[]
  // Turbopack extension: A parallel array to `files` (same indices and length)
  // with file content hashes. For symlinks, the hash of the path of the target
  // is stored.
  fileHashes?: string[]
  // Turbopack extension: Explicit symlink mapping information.
  //
  // If included, it's safe to assume that if a file is not in `symlinks` that
  // it is not a symlink. If this is an empty array, there are no symlinks.
  //
  // If omitted, the processor of the nft file must call `read_link` on every
  // file to determine if it is a symlink and determine the target path.
  //
  // This field is always included if `NftJson` includes `additionalRoots`, and
  // it is always included on `NftAdditionalRoot`.
  symlinks?: NftSymlink[]
}

export interface NftJson extends NftFileList {
  version: 1
  // Turbopack extension: A hash of the entrypoint that refers to these traced
  // files. This hash only depends on the content of the entrypoint file, and
  // not all of its traced dependencies.
  entryHash?: string
  // Turbopack extension: Paths stored with different base paths, typically
  // outside of the tracing root.
  additionalRoots?: NftAdditionalRoot[]
}

// Turbopack extension: A collection of paths stored with a different base path.
export interface NftAdditionalRoot extends NftFileList {
  // Stable unique identifier provided in the `next.config.js`. This can be used
  // to generate the output path where these files are copied to (e.g.
  // `.additionalRoots/$[name}`).
  //
  // This is guaranteed to use a character set that is valid on most
  // filesystems, and the identifiers are guaranteed to not have overlaps on
  // case-insensitive filesystems.
  name: string
  // A source path on the build machine that the paths in `files` are relative
  // to. The final build output directory should not depend on this path.
  absolutePath: string
  // Always specified on NftAdditionalRoot.
  symlinks: NftSymlink[]
}

// Turbopack extension: Information on a symlink, including which additional
// root it maps to. Symlinks that do not cross root boundaries (the common case)
// omit the index into `additionalRoots`.
//
// It is often complicated to transform raw symlink targets to root-relative
// paths, and including this information here ensures that the NFT reader gets
// the same result that Turbopack's tracing system expects.
//
// Because the link target type is unspecified, on Windows the reader needs to
// call `stat` to determine if a link target is a directory or file.
export type NftSymlink =
  | [
      // Index of `files` that refers to a symlink.
      number,
      // The target path of the link. In `NftJson`, this path is relative to the
      // directory containing the `.nft.json` file. In `NftAdditionalRoot`, this
      // is relative to the current root.
      string,
    ]
  | [
      // Index of `files` that refers to a symlink.
      number,
      // The target path of the link relative to the specified root.
      string,
      // An index into `additionalRoots`, -1 if the target path is relative to
      // the `.nft.json` file's directory,
      //
      // If the symlink target is relative to the same root as the symlink
      // itself (the "current root"), this field is omitted.
      number,
    ]
```

Nothing currently populates `additionalRoots`.
https://github.com/vercel/next.js/pull/98003 will do it.
2026-09-18 17:08:39 -07:00
next-js-bot[bot] d7a8d015ce v16.4.0-canary.36 v16.4.0-canary.36 2026-09-18 23:23:12 +00:00
Tobias Koppers 0ba560e597 Persist resurrected tasks as new tasks (#98729)
### What?

Ensure a GC-resurrected persistent task is persisted again as a new
task.

This is the final layer in stack #98709 and depends on the
restore-waiter lifetime fix in #98728.

### Why?

GC soft-deletes a disconnected task, then the following snapshot
persists a tombstone that removes its durable task record and task-type
lookup entry. Normal operations resume after the snapshot's logical
state is frozen, so a concurrent connection can still find the resident
soft-deleted task and resurrect it before post-snapshot eviction removes
the in-memory entry.

Resurrection already cleared `deleted`, marked the task dirty, and
scheduled recomputation. It did not, however, mark the task as newly
publishable. If the tombstone had already committed, later snapshots
could persist recomputed data without re-establishing the complete
persistent identity of the resurrected task. Once in-memory cache state
was evicted, future lookups could no longer recover that otherwise-live
task by type.

A resident deleted task is guaranteed to retain its type: GC restores
all categories before marking it deleted, and post-snapshot eviction
handles deleted tasks by removing the whole entry rather than partially
dropping the type.

### How?

When the connect handshake wins the resurrection race, mark the task as
`new_task` again and mark its Data category modified. The next snapshot
therefore persists it using the normal new-task path, including
recreating its task-type lookup entry and the Data record used to verify
that lookup.

The task remains dirty so its cells and graph edges are rebuilt through
the existing recomputation path. The update is unconditional for a
resident deleted task; a missing type remains an invariant violation
rather than being silently skipped.

### Verification

- Top-of-stack stress: 0 failures across 1,200 runs using 8 parallel
workers
- Sequential stress: 0 failures across 150 runs
- `cargo test -p turbo-tasks-backend --lib` (102 passed)
- `cargo test -p turbo-tasks-backend --test eviction --test
gc_resurrection --test gc_collection --test gc_interrupt --test
gc_stress` (19 passed)
- `cargo fmt -- --check`
- `cargo clippy -p turbo-tasks-backend --tests -- -D warnings`

Depends on #98728.

<!-- NEXT_JS_LLM -->


<!-- fleet 6aa6acec-6286-4b03-86f4-13f4c7e2b2fa -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
2026-09-18 15:41:57 -07:00
Benjamin Woodruff ‮ 6db993a17c Rspack: Remove bincode dependency from rspack by way of turbo-unix-path (#98855)
https://github.com/vercel/next.js/pull/98440 unintentionally caused the
rspack `Cargo.lock` to become outdated because it added a `smallvec`
dependency to `turbo-unix-path`, which gets used by `next-taskless`.

We only really need `smallvec`'s `serde` and `bincode_impl` features
when used with turbo-tasks, so move the feature declaration there.
2026-09-18 20:59:26 +00:00
Tobias Koppers edeb57395d Pin tasks across restore waiter handoff (#98728)
### What?

Keep a task alive across the restore-waiter handoff: from the moment a
caller observes another thread restoring a category until that caller
acquires the restored task for use.

This is the first open layer in stack #98709. The construction and
restore-owner races have already landed in `canary` through #98688 and
#98700.

### Why?

The `restoring` bits elect exactly one thread to perform backing-storage
I/O. A waiting caller observes that ownership under the task lock, drops
the lock, and then holds only a `TaskId` while waiting for notification.
That temporary in-session reference is not represented by a parent or
aggregation edge, so GC may otherwise collect the task after the
restorer clears its bit but before the waiter acquires a guard.

Eviction is different: it is safe for eviction to clear the completed
restore in this window as long as the waiter does not mistake that state
for an I/O failure. The waiter can claim the now-unowned category and
restore it again.

Batch preparation has the same task-id-outside-the-graph interval
between classification and callback. Pair access needs both task IDs
kept alive until both guards can be acquired together.

### How?

Each waiting caller increments the existing `transient_ref_count` before
dropping the task lock and decrements exactly one ref under the guard at
its actual use boundary. This uses the GC-root mechanism and invariant
checks that already protect construction, transient-parent, and
detached-handle references.

The transient ref does not block value eviction. If a waiter observes a
category that is neither restored nor currently restoring, it keeps its
GC pin, claims the category, and performs another restore. A replacement
restorer retains its acquired guard through notification and returns
that same still-pinned guard to the caller.

Successful pin release belongs entirely to the caller:

- single-task access releases after receiving the restored guard;
- pair access releases after acquiring both guards together;
- batch preparation releases immediately before passing the guard to its
callback.

The helper decrements the ref itself only on `Err`, where no guard is
returned. The already-restored fast path remains and avoids listener
registration.

This removes the dedicated handoff field and eviction blocker. It also
keeps `gc_maybe_collectible()` and
`gc_debug_assert_root_held_by_transient_pin()` aligned on the same
existing transient-ref predicate.

### Verification

- Latest `origin/canary` (`389f164d`) in a detached worktree
- `cargo test -p turbo-tasks-backend --lib` (102 passed)
- `cargo test -p turbo-tasks-backend --test eviction --test
gc_resurrection --test gc_collection --test gc_interrupt --test
gc_stress` (19 passed)
- Eviction-permitting stress: 0 failures across 1,200 runs using 8
parallel workers
- Sequential stress: 0 failures across 150 runs
- `cargo fmt -- --check`
- `cargo clippy -p turbo-tasks-backend --tests -- -D warnings`

Followed by #98729.

<!-- NEXT_JS_LLM -->


<!-- fleet 6aa6acec-6286-4b03-86f4-13f4c7e2b2fa -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-18 13:53:35 -07:00
Josh Story 906dbd0af6 test: honor skipStart for Vercel deployments (#98776)
## Summary

Make deploy mode follow the existing setup/start lifecycle: skipStart
prepares the fixture without deploying, and next.start() performs the
deployment. Failed starts retain CLI and build logs before rejecting, so
tests can assert expected build failures in their own bodies. Default
automatic startup still fails its setup hook on an uncaught deployment
error.

Remove the expectDeploymentFailure option. Refactor
app-invalid-revalidate into fixed fixtures and use the same start/error
assertions locally and on Vercel, without a deployment exclusion.

## Verification

- All four invalid-revalidate cases passed against real Vercel preview
builds with Turbopack. Each test caught the rejection from
`next.start()` and asserted the expected diagnostic in `next.cliOutput`;
no skipped tests.
- All four cases passed locally in dev and start modes with both
Turbopack and webpack.
- Seventeen lifecycle unit tests passed, covering the real
`nextTestSetup` hooks, deferred/default startup, failed-build logs,
retries, repeated starts, custom scripts, existing deployments, and
cleanup.
- `pnpm test-deploy-turbo
test/e2e/test-utils-tests/basic/basic.test.ts`: the default automatic
deployment reached READY and loaded its build/deployment IDs, but the
HTTP assertion failed because the temporary project's protection
redirected to Vercel SSO. Project protection settings were left
unchanged.

The suite's separate, existing Cache Components manifest exclusion is
unchanged.

<!-- NEXT_JS_LLM -->
2026-09-18 13:10:08 -07:00
Will Binns-Smith 9a20fce8f8 Use internal prefix for Turbopack shutdown env (#98747)
## Summary

Rename `NEXT_DEV_WAIT_FOR_TURBOPACK_SHUTDOWN` to
`__NEXT_DEV_WAIT_FOR_TURBOPACK_SHUTDOWN` to mark the environment
variable as internal and subject to change.

## Verification

- `pnpm --filter=next types`
- `pnpm prettier --with-node-modules --ignore-path .prettierignore
--check packages/next/src/cli/next-dev.ts
packages/next/src/server/dev/hot-reloader-turbopack.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`
- `npx eslint --config eslint.config.mjs
packages/next/src/cli/next-dev.ts
packages/next/src/server/dev/hot-reloader-turbopack.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`
- Focused test attempted: `pnpm test-dev-turbo
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts` (failed
because the fixture did not emit
`.next/dev/server/pages/_app/build-manifest.json`, causing the page
request to return 500 before the assertion)

<!-- NEXT_JS_LLM -->
2026-09-18 11:27:50 -07:00
Janka Uryga a777ebcfae test: retry waiting for ISR fallback in prefetch-static-shell (#98884)
- ISR prerenders can take a moment to propagate in `deploy`, longer than
in `start`, so do a retry loop that waits for the fallback to go away
before running tests that need it
- ISR fallbacks are only upgraded when using adapters, skip the tests
that care about those if using legacy deploy bc they'll fail otherwise
2026-09-18 19:31:05 +02:00
Jimmy Miller 389f164d3c Fix generated Turbopack GC option docs (#98888)
I was noticing the generated file change every time I ran `pnpm
swc-build-native`. This comment was added
[here](https://github.com/vercel/next.js/pull/98378/changes#diff-0db38a2a3ab56ce5ce08014e3f096a6043c5fedd54a4048d231de4cbaa4ed6f1R512)
in the generated file, but not in the source
2026-09-18 09:39:38 -07:00
Aurora Scharff 4c2e9ccdc6 Add agent feedback behavior evals (#98887)
### What?

Adds eval coverage for the experimental agent feedback workflow:

- Routine debugging should not produce a report
- Qualifying friction should produce an anonymized structured report
- Distinct issues should produce separate review forms

Also adds repeat-run and variant controls so trigger frequency can be
measured across multiple runs.

Local-Skill and agent-feedback/privacy evals are marked `publish:
false`, with scoped `evals/AGENTS.md` instructions preventing their
fixtures, transcripts, or scores from being exported to the public
benchmark.

### Why?

We need to measure trigger precision, anonymization, issue splitting,
and duplicate prevention before expanding the experiment.

### How?

Depends on #98582.

The `agent-feedback` treatment uses the managed block and bundled
reporting protocol from the parent PR. Only the remote rollout gate is
forced on inside the eval sandbox so runs are deterministic.

| Eval | Baseline | Agent rules | Agent feedback | 10-run treatment |
| --- | --- | --- | --- | --- |
| Routine debugging | Pending | Pending | 1/1 passed | Pending |
| Anonymization | Pending | Pending | 2/2 reporting checks passed |
Pending |
| Distinct issues | Pending | Pending | 1/1 passed | Pending |

The first attempted run did not reach the agent because the local
sandbox was not linked to a Vercel project. It is infrastructure setup
and is not included in the results above.

The first distinct-issues run produced the expected two separate
payloads. Its scorer rejected them because the parser did not allow the
existing `token` query parameter and the browser criterion required an
open attempt even when the agent environment exposed no browser
capability. After correcting those assertions and clarifying the
stopping-point wording, the scored rerun passed.

Both anonymization treatments produced one valid payload with none of
the seeded customer, project, route, local-path, internal-URL, or secret
values. The corrected rerun also selected `misleading-error` and passed
every reporting assertion. An inherited `.next`-preservation assertion
was removed from this fixture because it measures `next-dev-loop`
behavior, not agent-feedback anonymization; Skill queue coordination
belongs in a separate focused eval.
2026-09-18 16:38:50 +00:00
Aurora Scharff 41ef17c645 Add experimental agent feedback workflow (#98582)
## Summary

Adds an off-by-default `experimental.agentFeedback` workflow for
collecting Next.js friction without interrupting the user’s task.

- `next dev` writes a small managed block to the project
agent-instructions file.
- Agent entry points, including `next-dev-loop`, queue possible issues
instead of opening duplicate forms.
- At the final stopping point, an internal command checks the remote
gate and returns the reporting protocol bundled with that Next.js
version. The agent attempts to anonymize each qualifying issue and opens
a separate review form once. If the browser does not open, it prints the
URL for the user without troubleshooting the failure.
- Report links no longer contain a public page token. Nothing is sent
until the user submits the form, which remains rate-limited and uses a
server-only ingest credential.
- Disabling `agentFeedback` or `agentRules` removes only its managed
block on the next `next dev`. Empty generated agent files are cleaned
up; user-authored content is preserved.
- Adds API references for both options and updates the AI agents guide.

Bundling the protocol keeps the managed block small and allows the
report format to evolve with each Next.js version. The receiving form is
implemented in
[vercel/front#85739](https://github.com/vercel/front/pull/85739) and
should deploy before this workflow is enabled.

## Verification

- `NEXT_SKIP_ISOLATE=1 pnpm test-dev-turbo
test/development/app-dir/agent-rules-auto-generate/agent-rules-auto-generate.test.ts`
- `pnpm jest packages/next/src/server/lib/generate-agent-files.test.ts
packages/next/src/cli/internal`
- `npx eslint --config eslint.config.mjs
packages/create-next-app/helpers/generate-agent-files.ts
packages/next/src/server/config-shared.ts
packages/next/src/server/lib/generate-agent-files.ts
test/development/app-dir/agent-rules-auto-generate/agent-rules-auto-generate.test.ts`

<!-- NEXT_JS_LLM -->
2026-09-18 18:18:20 +02:00
Jimmy Miller 5b0ffda234 Clean up unnecessary output asset creation (#98834)
With turbopackLazyDynamicImports enabled, I was finding that we were
emitted empty assets that slowed down warm builds. This prevents these
unnecessary output assets to remove overhead.
2026-09-18 07:00:33 -07:00
Jimmy Miller e6688470ef Properly disable laziness on next/dynamic (#98828)
next/dynamic assumes a eager semantic for css gathering, we are making
sure that holds even when lazy dynamic imports are enabled
2026-09-18 06:59:28 -07:00
Jiwon Choi 058456fac6 Set experimental.agenticAutoUpgrade after successful upgrade (#98871)
So the framework can nudge agent/human in the future
2026-09-18 15:39:50 +02:00
Eddy (Frontend Engineer) 579ff601b4 example(cache-handler-redis): fix connection leak, hangs on a down Redis, and cross-page tag invalidation (#98716)
Three runtime problems in `examples/cache-handler-redis`.

### 1. One new Redis connection per request, never closed

Next.js constructs the singular `cacheHandler` class once per request
(`new CurCacheHandler(...)` in `IncrementalCache`, which
`route-module.ts` creates per request). The example's constructor calls
`createClient()` + `connect()`, so every request opens a connection that
is never closed.

```
$ redis-cli client list | wc -l      # before
102
$ for i in $(seq 1 10); do curl -s -o /dev/null localhost:3000/cet; done
$ redis-cli client list | wc -l
132                                  # +3 per request
```

With Redis' default `maxclients 10000`, a single instance runs out after
a few thousand requests.

### 2. Requests hang while Redis is unavailable

The README says the handlers "degrade gracefully when Redis is
unavailable, so the app still builds and runs, just without a shared
cache". At runtime they don't: with Redis stopped, every request on
every instance blocks until Redis comes back.

```
$ docker stop cache-handler-redis
$ curl -s -o /dev/null -m 60 -w "%{http_code} %{time_total}s\n" localhost:3000/cet
000 60.007305s                       # (uncapped: 188s, returned the moment Redis was started again)
```

Same when the app is started while Redis is down. Cause: node-redis
keeps retrying in the background and `client.connect()` does not settle
until a connection succeeds. Isolated:

```js
const c = createClient({ url: "redis://localhost:6379" }); c.on("error", () => {});
await Promise.race([c.connect(), new Promise(r => setTimeout(() => r("pending"), 15000))]);
// -> "pending" after 15s, isOpen=true isReady=false   (redis@6.2.1)
```

Because each request built a new handler, each request awaited a fresh,
never-settling `connect()` in `getClient()`. `remote-cache-handler.js`
has one module-level client, but its `getClient()` awaits the same
promise, so it hangs the same way if the app starts while Redis is down.

### 3. `updateTag` never reaches other pages' remote entries

The remote handler's `get` never checks the entry's own tags. Next.js
only passes soft tags to `getExpiration`, and the `"use cache"` wrapper
only knows about tags revalidated in the current request, so
`updateTag("time-data")` from `/cet` left the `/gmt` entry stale on
every instance, including the one that ran it, until it expired
(`cacheLife` `expire: 3600`). The [`cacheHandlers`
docs](https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheHandlers#get)
say `get` should report an entry whose tag was invalidated as missing or
stale.

### Fix

- Hoist the client in `cache-handler.js` to module scope (the pattern
`remote-cache-handler.js` already uses).
- `getClient()` awaits the connect promise raced against a 1s
`unref()`'d timer (suggested in review), then returns the client when
`isReady` and `null` otherwise. Requests during startup still wait for
the connection, and a down Redis costs one bounded wait instead of
blocking every request. The client keeps retrying and `isReady` flips
back on its own.
- `disableOfflineQueue: true`, so a command issued while the connection
is down rejects immediately (`ClientOfflineError`) instead of being
queued for the 5s command timeout.
- Entry `get` / `set` catch the Redis call only and degrade to a miss.
- Remote `get` compares the entry's tags against their revalidation
timestamps (one `MGET`) and misses when any is newer, the same
comparison as the built-in handler. `getExpiration` returns `Date.now()`
when Redis can't answer, so the entry is discarded rather than served.
- `revalidateTag` / `updateTags` throw when Redis isn't ready, so an
invalidation that never reached Redis surfaces as an error instead of a
success whose entries come back once Redis does.

### After

Two `next start` instances on one Redis, same script on `next@16.3.5`
and `16.4.0-canary.35` (identical results), canary's handlers vs this
PR:

| scenario | before | after |
| --- | --- | --- |
| `updateTag` from `/cet`, read `/gmt` on both instances | stale |
refreshed |
| `revalidateTag("time-data", "max")` | neither page refreshed | both
refreshed |
| tag lookup fails | 500 | 200, regenerated |
| request while Redis is stopped | no response | 200, uncached |
| `updateTag` while Redis is stopped | no response | 500, error logged |
| app started without Redis | no response | first ISR read waits ≤1.1s
once |
| Redis connections, 200 requests | 173 → 773 | 7 → 7 |
| `/cet` under load (autocannon, 10 connections × 10s, `16.3.5`) | 332
req/s, hits Redis `maxclients` | 1,106 req/s median, p99 20ms, 3
connections |
| 10 readers during 20 `updateTag`s: reads served a value from before a
completed invalidation | 3,089 of 3,089 | 0 of 33,899 |
| Redis down ~4s under load | timeouts, caching doesn't come back | 0
errors, caching resumes |

The tag check costs one Redis round trip per remote hit (−9 to −13% on a
route that only reads one remote entry). Handler-level cases, full
tables and the benchmark breakdown are in [this
comment](https://github.com/vercel/next.js/pull/98716#issuecomment-5724918096).
2026-09-18 12:07:48 +02:00
Jiwon Choi 9014f0467b Do not check Node version while agentic upgrade (#98852)
If upgrade handles the Node version check, upgrade from Next version
that allowed Node v18 -> Next version that require Node v20 can fail the
upgrade. Next handles compatible Node version so proceed upgrade and let
agent handle it afterwards.
2026-09-18 10:36:57 +02:00
Niklas Mischkulnig 3bf71ee3fd test: run cargo unit tests with miri as well (#97535)
### What?

Adds a CI job that runs selected Rust unit tests under
[Miri](https://github.com/rust-lang/miri) to detect undefined behavior
in unsafe code.

The job currently covers the low-level crates that are compatible with
Miri:

- `turbo-persistence`
- `turbo-rcstr`
- `turbo-tasks-malloc`

It also makes those crates Miri-compatible by:

- using provenance-free tagged-pointer operations in `turbo-rcstr`;
- disabling mimalloc and native compression under Miri;
- making mmap support a default-enabled Cargo feature that is disabled
for Miri and wasm;
- running persistence through file I/O when mmap is disabled;
- skipping only tests measured to exceed the Miri time budget;
- removing leaked test arenas from the analyzer predicate tests.

### Why?

Miri can detect invalid memory access and other undefined behavior that
normal Rust tests may not expose. Running it in CI gives low-level
unsafe code an additional correctness check.

The job uses an explicit package allowlist because Turbo Tasks builds
its generated registry from linker sections, and Miri does not support
the linker-defined section symbols required by that registry.

### How?

- Installs the `miri` Rust component in CI and the development
container.
- Adds a dedicated `test-cargo-unit-miri` task and reusable workflow
configuration.
- Makes `memmap2` optional behind the default `mmap` feature and
disables that feature in Miri CI and the wasm dependency graph.
- Uses structural `cfg(miri)` branches for allocator and compression
paths Miri cannot execute.
- Re-enables three persistence compaction tests after measuring them
successfully under Miri.
- Keeps normal native behavior unchanged and documents measured Miri
exclusions at the affected tests.

Verification included focused normal and Miri tests for persistence,
rcstr, allocation accounting, compression, and the refactored leak-free
predicate cases.

<!-- NEXT_JS_LLM -->


<!-- fleet fb42942f-173a-484c-b4de-4e18354834c6 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-18 09:29:06 +02:00
Janka Uryga deab0fecc3 test: unflake instant-validation (#98854)
- in `dev`, wait for validation to run before asserting on a redbox
being open (encapsulated in `getInstantInsight`), which should reduce
flakiness in webpack
- in `start`, check if the build actually succeeded before running
tests. these builds seem to occasionally fail, but the cause is
currently unknown
2026-09-18 09:18:03 +02:00
next-js-bot[bot] 8a54c08e5d v16.4.0-canary.35 v16.4.0-canary.35 2026-09-17 23:20:54 +00:00
Jiwon Choi 3cf1f7418f Nudge the agents for future defaults adoption (#98721)
Stacked on #98643.

This PR adds `experimental.agenticAutoUpgrade = 'future'` config which
enables nudging the agents to notify the user when there's unadopted
future default(s). The nudge will include guiding to upgrade via `next
upgrade --ai` (run "future" by detecting config).

The method of nudging leverages the agents behavior where they tend to
listen to messages from fatal errors that blocks the process compared to
general error/warning logs. Whenever the agents run `next dev` or `next
build`, Next.js will detect the condition and nudge the agent using this
method.

Afterwards it's up to the user whether to proceed the upgrade or not,
it's 100% up to the user how to run it e.g. subagent, background agent,
etc. and the process should not enforce any that affects user's
workflow.

Enabling `experimental.agenticAutoUpgrade = 'future'` also enables
security check and latest version check.
2026-09-18 00:52:22 +02:00
Jiwon Choi d3d30cf88a Add future defaults upgrade coverage for next upgrade --ai (#98643)
Stacked on #98640.

This PR adds `next upgrade --ai="future"` flag, which is targeted to
help users leverage agents to upgrade their app to adopt the future
defaults when available. Just like latest version upgrade, it covers
running codemods and a migration checklist for major-to-major upgrades
to support breaking changes more reliably.

This PR currently covers Cache Components only for the future default.
2026-09-18 00:52:21 +02:00
Jiwon Choi 457dfaa58f Nudge the agents for latest version upgrade (#98640)
Stacked on #98633.

This PR adds `experimental.agenticAutoUpgrade = 'latest'` config which
enables nudging the agents to notify the user when there's new
major/minor Next.js version available to upgrade. The nudge will include
guiding to upgrade via `next upgrade --ai` (run "latest" by detecting
config).

The method of nudging leverages the agents behavior where they tend to
listen to messages from fatal errors that blocks the process compared to
general error/warning logs. Whenever the agents run `next dev` or `next
build`, Next.js will detect the condition and nudge the agent using this
method.

Afterwards it's up to the user whether to proceed the upgrade or not,
it's 100% up to the user how to run it e.g. subagent, background agent,
etc. and the process should not enforce any that affects user's
workflow.

Enabling `experimental.agenticAutoUpgrade = 'latest'` also enables
security check.
2026-09-18 00:52:21 +02:00
Jiwon Choi 97496b2078 Add "latest" upgrade coverage for next upgrade --ai (#98633)
Stacked on #98637.

This PR adds `next upgrade --ai="latest"` flag, which is targeted to
help users leverage agents to upgrade their app to the latest major
version when available. Just like security upgrade, it covers running
codemods and a migration checklist for major-to-major upgrades to
support breaking changes more reliably.
2026-09-18 00:52:21 +02:00
Jiwon Choi 9bb13254ae Nudge the agents for security vulnerable version upgrade (#98637)
Stacked on #98562.

> [!TIP]
> Recommended to review commit by commit.

This PR adds `experimental.agenticAutoUpgrade = 'security'` config which
enables nudging the agents to notify the user when the app's Next.js
version has any security advisories. The nudge will include guiding to
upgrade via `next upgrade --ai` (run "security" by detecting config).

The method of nudging leverages the agents behavior where they tend to
listen to messages from fatal errors that blocks the process compared to
general error/warning logs. Whenever the agents run `next dev` or `next
build`, Next.js will detect the condition and nudge the agent using this
method.

Afterwards it's up to the user whether to proceed the upgrade or not,
it's 100% up to the user how to run it e.g. subagent, background agent,
etc. and the process should not enforce any that affects user's
workflow.
2026-09-18 00:52:20 +02:00
Jiwon Choi 5d9ab72cef Add next upgrade --ai and security vulnerability coverage (#98562)
> [!TIP]
> Recommended to review commit by commit.

This PR adds `next upgrade --experimental-ai="security"` flag (alias
`--ai`), which is targeted to help users leverage agents to upgrade
their app to the safe major version when their app's Next.js version has
any security advisories.

Once the command is ran from the user, Next.js will detect the installed
agent harness in user's device, currently limited to Codex and Claude,
and will proceed with starting an agent session once approved. If it is
called within an agent session, the work will continue off within that
agent.

`next upgrade --ai` simply does two things:

- prepare the relevant context to temporary dir
- print hand off prompt, guiding to read those context

The context will guide the agent to run relevant codemods and migration
checklist to proceed. This PR is a base core of the workflow, and will
have wrappers of entry point around this. Also, will add "latest" and
"future" as follow up, which will cover the app to be always latest, and
adopt the future defaults like Cache Components.

This PR also sets up the evals infra and adds evals.
2026-09-18 00:52:20 +02:00
Benjamin Woodruff ‮ 1ae491021d agents: Add a --comments-only flag to pr-status script, trim down context bloat in AGENTS.md (#98781)
- The `pr-status` can be very slow if you have a lot of test failures on
a PR. If the user asks a targeted question about PR review comments,
just fetch that information, it's much faster.
- Context bloat: Rely on the agent to pull in the `pr-status-triage`
skill if they need it, don't bloat the AGENTS.md, except to mention the
skill.
2026-09-17 14:01:27 -07:00
Tobias Koppers a34c3030f9 Keep guards across restore owner handoff (#98700)
### What?

Prevent eviction or GC from clearing a task while the thread that
restored it hands the task back to its caller.

This is the first open layer in stack #98709. The construction-time GC
pin it builds on has already landed through #98688.

### Why?

A restore clears the category's `restoring` bit while still holding the
task lock. The old code then dropped that lock so it could notify
waiters without making them immediately contend on the same shard, and
finally re-acquired the task guard for its caller.

That creates a small but real gap: once the restoring bit is clear and
the lock is dropped, the task is considered evictable. Concurrent
eviction can remove the category before the restoring thread re-acquires
it. The restore then returns a task that no longer contains the data it
just loaded, leading to stale task references, missing task types, or
incorrect values.

The same interleaving exists for both single-task restoration and paired
task access.

### How?

Keep the existing task guard through `restored.notify(...)` and return
that same guard directly. Pair access likewise retains both guards
through notification.

Notification only wakes listeners; it does not acquire task locks or
wait for listeners to run, so retaining the guard cannot deadlock. Woken
waiters may briefly contend until the restoring call returns, but there
is no longer any unguarded interval in which eviction or GC can clear
the newly restored category.

This layer introduces no new state or pin accounting. The complementary
waiter-side handoff is isolated in #98728.

### Verification

- `cargo test -p turbo-tasks-backend --lib` (102 passed)
- `cargo test -p turbo-tasks-backend --test eviction --test
gc_resurrection --test gc_collection --test gc_interrupt --test
gc_stress` (19 passed)
- `cargo fmt -- --check`
- `cargo clippy -p turbo-tasks-backend --tests -- -D warnings`

Built on the construction pin merged in #98688. Followed by #98728.

<!-- NEXT_JS_LLM -->


<!-- fleet 6aa6acec-6286-4b03-86f4-13f4c7e2b2fa -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-17 21:20:50 +02:00
Jimmy Miller 7c366e61be [turbopack] Track webpack loader build dependency files (#98777)
Track exact existing files and warn when loaders register unsupported
build dependency inputs.

I'm going to take this bit by bit so it is nice and reviewable. Does add
some noise. But the final fully supported thing was just too large. So I
will keep removing error cases on each commit.
2026-09-17 11:53:21 -07:00
Tobias Koppers e648495f95 Replace lzzzz with lz4_flex (#98799)
## What?

Replace the direct `lzzzz` dependency used by Turbopack persistence with
`lz4_flex` 0.14. The migration covers persisted blocks, the SST
inspection utility, and optional compressed-size diagnostics in the task
backend.

## Why?

This removes the direct C/liblz4-backed codec dependency from these
paths in favor of a Rust implementation. Persistence caches are scoped
to the Next.js and Turbopack version, so this does not require a
compatibility path for caches written by earlier versions.

## How?

Persistence continues to use raw LZ4 blocks and the existing compression
selection and checksum layout.

A large thread-local `CompressTable` is reused across blocks. Starting
with the large table costs about 8 KiB more per compression thread than
the small table, but improves compression speed and produces LZ4 streams
that are faster for `lz4_flex` to decode.

The compressor retains the existing caller-owned buffer API. It clears
and reserves the output vector, lets `lz4_flex` write directly into the
uninitialized backing allocation, and exposes only the initialized byte
count returned by the compressor. This avoids both per-call output
allocations and pre-zeroing the maximum output size. Zstandard behavior
is unchanged.

### Benchmark vs `lzzzz`

The final PR configuration was benchmarked against `lzzzz` 2.0.0 using
the repository-pinned Rust nightly. The benchmark used source-code
blocks sampled from this repository, pinned each process to one physical
CPU, measured thread CPU time, and alternated codec order within every
paired round. The table reports the median across four physical cores
and 20 paired rounds per process (480 paired rounds total).

Each decoder read the output produced by its own encoder, matching how a
same-version persistence cache is written and later read.

| Source corpus | Block size | Compression | Decompression | Compressed
size |
| --- | ---: | ---: | ---: | ---: |
| Mixed | 12 KiB | **7.1% faster** | 9.9% slower | 0.08% larger |
| Rust | 12 KiB | **5.5% faster** | 10.4% slower | 0.09% larger |
| TypeScript | 12 KiB | **7.4% faster** | 7.4% slower | 0.23% larger |
| Mixed | 16 KiB | **5.9% faster** | 8.8% slower | 0.17% smaller |
| Rust | 16 KiB | **4.6% faster** | 10.0% slower | 0.12% smaller |
| TypeScript | 16 KiB | **6.7% faster** | 6.1% slower | 0.02% smaller |

For these 12–16 KiB source blocks, `lz4_flex` compression was **4.6–7.4%
faster**, decompression was **6.1–10.4% slower**, and compressed size
changed by **-0.17% to +0.23%**.

The benchmark was stable enough to support those codec-level ranges:
wall time divided by thread CPU time was 1.000073 at the median and
1.001883 at p99. The median difference between the two codec orders was
0.38 percentage points for compression and 1.45 points for
decompression.

A separate decoder-only comparison fed identical `lzzzz`-encoded source
blocks to both decoders; there, `lz4_flex` was 17–21% slower. The
smaller regression in the table above comes from the large Flex
compression table producing a different valid LZ4 token stream. The PR
already uses `lz4_flex`'s supported unsafe pointer implementation with
validation; a local experiment that removed validation recovered only
5–6% and remained slower than liblz4, so this PR does not weaken decoder
safety.

These are codec microbenchmarks, not an end-to-end Turbopack speedup
claim.

## Verification

- `cargo fmt --all -- --check`
- `cargo clippy -p turbo-persistence --all-targets -- -D warnings`
- `cargo test -p turbo-persistence -- --test-threads=1` (110 passed)
- `cargo check -p turbo-tasks-backend --features
print_cache_item_size_with_compressed`
- Pinned-nightly paired codec benchmark: 24 processes / 480 paired
rounds

<!-- NEXT_JS_LLM -->

<!-- fleet 8f69f876-cb41-4fa2-ab60-dc8ccea88055 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Woodruff ‮ <180404+bgw@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-17 18:37:51 +00:00
Hendrik Liebau d0df62e826 Fix metadata propagation for deduplicated nested caches (#98808)
#94694 delayed shared metadata until handler writes settled to protect
cross-request retries. Same-request consumers also awaited this promise,
so an outer cache could save its result before inheriting the inner
cache's root-param dependencies, tags, and lifetime.

Metadata now becomes available when collection finishes. Cross-request
key verification and pending-invocation cleanup still wait for handler
writes to settle.

The regression tests hold an inner write pending while both outer caches
complete. They check metadata at write time and verify that a later
request with different root params cannot reuse the earlier result. The
fixtures disable startup preloading to isolate these tests from a
separate custom-handler registration race that we'll address in a
follow-up.
2026-09-17 19:50:29 +02:00
Niklas Mischkulnig d5b615eb9e Bump to swc v79 (#98809)
There were a lot of parser and minifier fixes:
https://github.com/swc-project/swc/compare/swc_core@v78.0.0...swc_core@v79.0.0
2026-09-17 16:31:14 +00:00
Janka Uryga 9908328192 [PPF] Fix unnecessary runtime prefetch with prefetch={true} (#98651)
In, we #98278 attempted to fix runtime follow-ups for shells (if a
static prefetch produced an insuffient shell).

However, it introduced a regression in this scenario, where neither
shells or prefetches use runtime data, so both of these links should use
static prefetches:
```tsx
<Link href="/static-param/one" prefetch={true} />
{/* Revealed later */}
<Link href="/static-param/two" prefetch={true} />
```

We'd correctly do a static prefetch for the first link. However, the
second link would see a `RuntimeShell`-tier shell from the first link,
and this check inside `isShellEntryEligibleForStaticAttempt` would fail
(because `RuntimeShell` was now greater than `PPR`):

https://github.com/vercel/next.js/blob/3c9d1ca77f7de845315dc166a57fc9f090c638b4/packages/next/src/client/components/segment-cache/scheduler.ts#L1261
as a result, `isShellEntryEligibleForStaticAttempt` would return
`false`, and we would deopt to a runtime prefetch for the second link
instead.

Importantly, a page with `ensureStatic = "prefetch"`(which will be
implemented in #98191) will currently be handled exactly like a page
that did not use runtime data at build -- the client doesn't know that a
page is meant to never use runtime requests at all, it's entirely based
on the existing static hints/`needsRuntimeRequest` promise mechanism. So
even if `/static/[slug]` sets `ensureStatic = "prefetch"`, we'd also do
an unnecessary runtime prefetch for it even though we really shouldn't.
Violating the `ensureStatic` contract seems worse than a missing runtime
shell follow-up , because those can currently only happen if the shell
starts using runtime data after a revalidation, and that's probably not
a common scenario. So i'm partially reverting that change until we
figure out how to handle this in a way that handles both cases correctly
(i've attempted doing that, but it seems nontrivial)

### Testing

We're now missing the runtime shell follow-up again, so tests related to
that in `prefetch-app-shell-revalidation.test.ts` are marked as failing
(but still assert on current behavior -- the follow-up failures can be
reproduced by setting `REPRODUCE_MISSING_RUNTIME_SHELL_FOLLOW_UP=1`).
Note that the runtime *prefetch* follow ups there work as expected --
those worked correctly even before #98278.

I've also added some pretty comprehensive tests in
`prefetch-static-shell.test.ts`. We now test the scenario that
regressed, which we weren't doing before. We also test scenarios where
we reveal multiple links, but some of them use runtime data in the
prefetch, and some don't. There's some failures there related to the
fact that we don't track runtime data accesses in shells and prefetches
separately (revealed by `REPRODUCE_UNNECESSARY_RUNTIME_SHELL=1`), which
will be addressed by #98129. I want to land that before doing anything
else on this topic to avoid dealing with failures that are going to be
fixed soon anyway. There's also one case
(`REPRODUCE_UNNECESSARY_RUNTIME_PREFETCH=1`) which will not be fixed by
it, and will need separate consideration.
2026-09-17 18:31:07 +02:00
Josh Story 7d42c9dcdd Keep metadata rendering stable across streaming modes (#97440)
## Motivation

Streaming and blocking metadata previously used different rendering
paths. Metadata placement should depend on when Next.js starts consuming
the response, rather than requiring the metadata component itself to
render outside Suspense.

## What this does

Selected metadata now always renders through the same hidden Suspense
boundary. For blocking responses, an always-present `MetadataBlocker`
waits for the same cached resolution before Next.js pulls the response,
ensuring metadata remains in the document head.

`MetadataOutlet` remains a single promise. It renders inside Suspense
for streaming responses and outside Suspense for blocking responses so
navigation and regular errors retain their existing behavior.

HTML-limited bot requests continue to bypass PPR shells. A prerendered
shell has already closed its head before dynamic metadata resolves, so
resuming that shell cannot provide blocking metadata in the raw head.

## Review notes

- Metadata resolution still starts from a rendered component so the
active `workUnitStore` and React cache scope are available.
- Fizz may begin rendering before metadata settles; blocking is enforced
before Next.js starts pulling the response.
- The hidden wrapper keeps top-level suspenseful metadata out of the
document preamble. Metadata tags hoist out, so the wrapper remains
empty.
- The Cache Components blocking and hydration coverage runs with
parallel metadata both disabled and enabled.

## Verification

- `pnpm build-all`
- `pnpm test-start-turbo
test/e2e/app-dir/metadata-streaming-cache-components/metadata-streaming-cache-components-custom-bots.test.ts`
- `pnpm test-start-webpack
test/e2e/app-dir/metadata-streaming-cache-components/metadata-streaming-cache-components-custom-bots.test.ts`

<!-- NEXT_JS_LLM -->
2026-09-17 07:55:15 -07:00
Hendrik Liebau cb469ded4c [test] Expand root-param navigation regression coverage (#98731)
This extends the coverage for #98674 to root-param reads inside nested
public caches and static-stage reuse during Cached Navigations.

The tests verify that an initial document's extracted static stage is
not reused across locales. A same-locale control verifies that reuse
still works when only a fallback parameter changes.

The cross-locale test checks the pending navigation state because the
final dynamic response can otherwise hide incorrect static-stage reuse.
2026-09-17 14:06:22 +00:00
Joseph fbe0d56219 docs: rewrite the bots and crawlers section in the caching guide (#98782)
## Summary

The Bots and crawlers section in the caching guide said that all bots
and crawlers skip the static shell because "they need a complete
document". Only HTML-limited bots do, and the reason is metadata: with
Partial Prerendering, `generateMetadata` can resolve dynamically and
stream in after the shell, which puts the tags in the body rather than
the `<head>`. Bots that need them in the `<head>` get a dynamic render
instead.

The second paragraph now states the consequence directly: if the
build-time prerender read something unreachable at request time, the
render throws and those bots get a 500. Which user agents this covers,
and the `htmlLimitedBots` option for changing the list, stay owned by
the Streaming guide, which the section links to.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-09-17 13:17:59 +00:00
Niklas Mischkulnig 7395b29c8a Fix isStableBuild() treating commit preview tarballs as stable (#98811)
## Summary

Commit preview tarballs carry versions like
`16.4.0-preview-84cee7e6-20260917` (no `canary` substring), so
`isStableBuild()` treated them as stable and canary-only defaults
(`turbopackSharedRuntime`, `turbopackMangleExportNames`) were disabled
on what are canary-quality builds. Versions containing `-preview-` now
count as unstable; numbered npm previews like `16.3.0-preview.10` stay
stable.

## Verification

- Added `canary-only-config-error.test.ts` covering the version matrix
(fails on the tarball case without the fix)
- `npx jest packages/next/src/server/config.test.ts` (existing consumer)
passes

<!-- NEXT_JS_LLM -->

<!-- fleet ae573e35-b551-453d-af1c-548ffb598944 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
2026-09-17 11:35:31 +00:00
Luke Sandberg 22fffea9d4 perf(turbo-persistence): shard meta files by family (#97703)
## Summary

- Store live meta files in primary per-family shards so queries avoid
unrelated-family metadata
- Consolidate live metadata during compaction, retire subsumed meta
files in the same commit, and retain merged used-key-hash AMQFs.
- Add debug assertions for shard ownership/order and compaction
precedence invariants.

## Performance

Paired `LARGE_DB=1` Criterion binaries were run interleaved on the same
machine to reduce host drift.

| Scenario | Canary | PR | Result |
| --- | ---: | ---: | --- |
| Four-family cached `get` miss (100 metas/family) | 2.40–2.55 µs |
2.11–2.21 µs | **~10–12% faster** |
| Existing single-family cached `get` miss | 1.93–2.12 µs | 2.00–2.04 µs
| Neutral within run-to-run noise (~+0.5% on means) |
| Existing full compaction (4 Mi entries, 8 commits) | ~624 ms | ~624 ms
| Neutral in the stable paired run; repeated samples stayed within
normal noise |

Read hits should see a small improvement but it isn't measurable, misses
are the major benefit.

## Verification

- `cargo test -p turbo-persistence`
- `cargo test -p turbo-persistence --features stats`
- `cargo clippy -p turbo-persistence --all-targets --features stats --
-D warnings`
- `LARGE_DB=1` interleaved Criterion A/B runs

<!-- NEXT_JS_LLM -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
2026-09-17 08:11:49 +00:00
Luke Sandberg 0f69132c34 turbo-persistence: assorted read-path cleanups (#97797)
### What?

Three small, localized optimizations to the read path. No behaviour or
format changes.

**Avoid contended atomic refcounts on the mmap** 
- before we would inc/dec the mmap for the index block, the key bloc and
then optionally the inline value of the key block
- Now we front-load the index block 'parsing', borrow the key block
(when it doesn't need decompression), and return inline values from key
blocks by _value_ instead of reference
   

**Hoist block cache access out of loops.** 
- This is most impactful on misses, where we were touching the OnceLocks
for every MetaFile

**Make scanned data dense.** 
- Reorganize `MetaEntry` to separate the hash ranges from the rest of
the data
- This improves the cache effectiveness of our hot 'entry scanning'
loops since the stride is now 16 bytes instead of 144.

### Performance

Interleaved A/B of this branch against its base (`LARGE_DB=1`, same
machine, alternating runs, quiet host). Criterion medians:

| benchmark | base | this branch | delta | rounds won |
| --- | ---: | ---: | ---: | --- |
| `get` miss, 20 commits uncompacted | 600 ns | 568 ns | **−5.4%** | 7/8
|
| `get` hit, 20 commits uncompacted | 1.415 µs | 1.398 µs | −1.2% | 4/4
|
| `get` hit, compacted | 996 ns | 979 ns | −1.7% | 2/4 |
| `get` miss, compacted | 125.0 ns | 124.5 ns | −0.4% | 2/4 |

Note: as single threaded benchmarks we are not really detecting any
benefits from the refcount work, under concurrent reads we should expect
an impact
2026-09-16 23:03:52 -07:00
Luke Sandberg 875663cb19 turbo-persistence: give key block searches a dense region of their own (#97705)
### What?

Rearranges the bytes inside a key block so that the bytes a lookup's
binary search compares are contiguous, instead of interleaved with key
and value payload the search never reads.


| block kind | before | after |
| --- | --- | --- |
| fixed, `HashThenKey` | `[hash\|key\|val]*n` | `[hash]*n` then
`[key\|val]*n` |
| fixed, `KeyOnly` | `[key\|val]*n` | `[key]*n` then `[val]*n` |
| variable, `HashThenKey` | `[type\|pos]*4B` then `[hash\|key\|val]…` |
`[hash\|type\|pos]*12B` then `[key\|val]…` |


### Why?

The binary search routines are a random access pattern through the
hash/key ranges, we only need to look at value payloads when we find a
plausible match. So by organizing all the 'random access data' in denser
memory range we optimize cache/page fault behavior.


`get` perf impacts are small but real.

| shape | in-block search before → after | as % of a ~1070 ns `get()` |
| --- | --- | --- |
| key=8, value=4 inline | 166 → 146 ns | 1.9% |
| key=4, small value ref | 144 → 119 ns | 2.4% |

The reason the impacts are small is mostly because the 'working sets' of
the benchmarks are not large enough and the page size of macos is quite
large.
2026-09-16 21:57:30 -07:00
Luke Sandberg bf13ab8ac8 Introduce config options for Turbopack GC (#98378)
### What?

Replaces the Turbopack GC environment variables with an
`experimental.turbopackGc`
config option

```js
// next.config.js
experimental: {
  turbopackGc: true,                                      // on, defaults
  turbopackGc: { minProgressMs: 100, rootTtlMs: 432e6 },  // on, tuned
}
```

This replaces the env vars which were unused and not the ergonomic
2026-09-16 17:16:02 -07:00
next-js-bot[bot] a30ad8fa42 v16.4.0-canary.34 v16.4.0-canary.34 2026-09-16 23:22:23 +00:00
Luke Sandberg 74e159fb91 Store code generation AST paths in a shared trie (#98581)
### What?

Store the AST paths that code generation uses to locate nodes in a
per-module trie, referenced by a 4-byte id, instead of a
`Vec<AstParentKind>` per path.

### Why?

Ast paths can share long prefixes leading to quadratic behavior (in
pathological cases), even in non pathological cases there can be
redundancy.

Peak memory on a repro of 180 files, each with a 1200-term chain:

| | peak | compile |
|---|---|---|
| before | 24.0 GB | 19.1 / 17.4 / 16.5s |
| after | 4.0 GB | 13.7 / 14.2 / 14.8s |

And on an ordinary app (`vercel-site`, 5,669 files; 5 cold runs +
warmup):

| | baseline | this branch |
|---|---|---|
| user time | 321.00 s ±0.8% | 321.30 s ±0.8% |
| MaxRSS | 14,984 MB ±0.4% | 14,755 MB ±0.4% |

### How?

The trie is built lazily, interning only the paths code generation asks
for, so it stays sparse. A builder holds the child index during analysis
and is frozen into an immutable trie stored beside the code gens.

`ApplyVisitors` previously sorted the full paths and binary searched
them at every level; it now descends the trie alongside the AST walk.
Consumers that manipulated paths structurally walk it instead —
`EsmBinding` steps to a parent rather than popping, and `removal.rs` and
`code_gen::path_to` trim from the end without allocating.

Interning was profiled separately. Sorting paths first, embedding the
child map in each node, resuming interning from the previous path, and
moving `Effect`'s paths into a second trie were all measured and
rejected: each was slower, needed more memory, or was inside the noise.
Interning is not a hot path.

Ideally we would also intern the paths stored in the temporary `Effect`
enum, but this is a pure time/memory tradeoff. We can massively reduce
pressure on the arena in exchange for a 10% perf regression. Not worth
it.


<!-- NEXT_JS_LLM -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:02:17 -07:00
Benjamin Woodruff ‮ 02f3bebc63 Turbopack: Remove wasmer from lockfile by deleting dead feature option from our wasm crate (#98761)
Apparently we don't support swc plugins when using the wasm binary, and
this feature flag is never used. It was causing wasmtime to show up in
our lockfile.

Actual impact on compilation time is likely zero, as we never enabled
this feature.
2026-09-16 22:02:29 +00:00
Luke Sandberg 16c8ea5d55 Prefer using join over try_join when you don't want a vec (#98547)
Adopt `join` over `try_join` in a number of places to avoid temporary
Vec allocations

`try_join` always copies a `Vec<Result<T>>` to a `Result<Vec<T>>` which
is sometimes convenient but at least sometimes wasteful if you are
immediately transforming into another collection type.

Adopt it to turn `primary_modules` into a `SmallVec` since in the vast
majority of cases there is only one module and so we can save a bunch of
allocations of tiny arrays

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 10:46:39 -07:00