Commit Graph

35737 Commits

Author SHA1 Message Date
Luke Sandberg 549476b05b turbo-tasks-backend: fix strongly consistent read hanging on a canceled task (#98312)
### What

Fixes a shutdown hang in `next build`: a strongly consistent read could
wait forever on a task that shutdown had canceled, which in turn blocked
`stop_and_wait` and hung the build until the test timeout.

Also makes the `hanging_detection` feature usable — it panics due to a
`data` access check today.

### Why the read hangs

1. Shutdown sets `stopped`. A task dispatched after that point is
canceled before it runs, so it has no output.
2. Its parent completes and connects its children.
`process_new_children` uses `!child.has_output()` as the test for "not
computed yet" and marks such a child `InitialDirty` so it gets
scheduled.
3. `make_task_dirty` promotes the canceled task's `SessionDependent` to
plain `Dirty` and clears `current_session_clean`. That is correct for a
live task that must re-run, and wrong for one that never will.
4. `AggregatedDataUpdate::from_task` then snapshots `dirty_state() ==
(true, false)` and contributes a dirty container that is not
session-clean.
5. The parent is itself clean, so its own `update_dirty_state(None ->
None)` early-returns and never evaluates `all_clean_event`. Its
dirty-container count never drains.
6. The strongly consistent reader waits on that event forever, holding a
foreground job that `stop_and_wait` blocks on.

`has_output()` is a proxy for "needs computing", and a canceled task
breaks that assumption.

### The fix

Record a terminal error output when a task execution is canceled. 

Make hanging_detection more reliable
2026-09-07 10:04:47 +02:00
next-js-bot[bot] 4fed8eaf19 v16.4.0-canary.19 v16.4.0-canary.19 2026-09-05 23:19:23 +00:00
Dominik K. a8d189769f fix: route info segment overrides not updating in dev overlay (#96968)
## Summary

Previously, when using the built-in route segment override in the
Next.js dev tools (Route Info panel), the UI was not updating: after
toggling a boundary (e.g. `loading.js` or `not-found.js`) you couldn't
switch back unless you closed the modal and reopened it, which slowed
down my workflow. It's not a huge issue, but I thought I'd fix it.

The cause was the segment explorer trie mutating nodes in place, so the
panel's memoized view never picked up insertions until remount. Trie
updates now copy the nodes along the mutated path (copy-on-write) so
changed nodes get fresh identities while untouched subtrees stay shared.

### Before

Switching from one override to another works, but the UI doesn't update
— you have to close and reopen the panel.


https://github.com/user-attachments/assets/39e43ad7-2f14-4b54-bdab-eba51c87a060

### After

Setting an override updates the DevTools UI immediately — no need to
close and reopen the panel.


https://github.com/user-attachments/assets/113887cf-fe07-4d9b-90a5-405b53692a07

## Verification

- `pnpm jest
packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.test.tsx`
- `pnpm test-dev-turbo
test/development/app-dir/segment-explorer/segment-explorer.test.ts`
- Manually verified in a dev app: toggling boundary overrides and "Clear
Segment Overrides" now update the panel live.

<!-- NEXT_JS_LLM -->
2026-09-05 20:43:15 +02:00
next-js-bot[bot] 6685283fe8 v16.4.0-canary.18 v16.4.0-canary.18 2026-09-04 23:20:28 +00:00
Xanaado 0ac302d3d8 Turbopack: fix source field in CSS sourcemaps (#88225)
Closes #88294


Currently, CSS source maps in Turbopack always use index `0` for all
source file references. This can lead to misleading mappings and makes
debugging CSS generated from multiple sources more difficult.
2026-09-04 22:54:25 +02:00
Janka Uryga 00b29d6654 fix: track dynamic accesses in final runtime prerenders (#98255)
The fix implemented in #95665 (`params/searchParams` not hanging
correctly in Runtime App Shells during prospective prerenders) was
incomplete. We started using `makeHanging[Search]Params` in the
prospective prerender (if it was an app shell and wouldn't resolve
them), but not during the final prerender. This meant that, when
encoding the cache key, we would only fire `dynamicAccessAbortSignal` in
the prospective prerender:


https://github.com/vercel/next.js/blob/090f1b72753b2d0094235d27d46ffa0da5fc7d35/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2160-L2174

...but the final prerender wasn't using `makeHanging[Search]Params`, so
it would not hit this early exit. The hanging input abort signal would
still correctly abort the key encoding (because it accounts for
`stageController.finalStage`) so the key would still be partial, as
expected. We would continue on, and attempt to look the key up in the
Resume Data Cache. But the prospective prerender exited early and never
put anything under that key, so we would incorrectly end up here, and
log:


https://github.com/vercel/next.js/blob/090f1b72753b2d0094235d27d46ffa0da5fc7d35/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2730-L2734

This is fixed by consistently using `makeHanging[Search]Params` if the
runtime prerender won't reach the stage where they resolve, i.e. if it's
an App Shell. This means we'll trigger `dynamicAccessAbortSignal` while
encoding the cache key in *both* renders, so we'll do the early exit
consistently and avoid the log.

(This feels a bit unsatisfying, because ideally this would all just work
based on stages, which is what I idealistically thought when writing the
code originally. But unfortunately our usage of
`dynamicAccessAbortSignal` for `params/searchParams` requires these
special cases to work consistently. Maybe this can be improved in the
future.)

### Tests

This scenario is covered by the existing `prefetch-runtime` tests -- i
discovered the bug by running them and seeing the `Unexpected cache miss
after cache warming phase` warning. We now assert that the warning is
not logged. The assertion is negative and would also pass if the error
message changed, so i've exported the message as a constant to ensure
`use-cache-wrapper` and the tests stay in sync.
2026-09-04 22:34:16 +02:00
Joseph 602a2aba90 docs: add API reference pages for skipProxyUrlNormalize and skipTrailingSlashRedirect (#97967)
- Generated with the `/write-api-reference` skill
- These two options were only listed in
https://nextjs.org/docs/app/api-reference/file-conventions/proxy#advanced-proxy-flags

Closes: https://github.com/vercel/next.js/issues/56090
2026-09-04 15:43:03 +02:00
next-js-bot[bot] 1b5400c926 v16.4.0-canary.17 v16.4.0-canary.17 2026-09-04 08:36:41 +00:00
Hendrik Liebau cc27d89b5d Turn on the adapter route collapses by default (#98218)
`experimental.collapseAdapterRoutes` now defaults to `true`, so a build
serves several dynamic routes from one entry in the route table that it
passes to an adapter. The default started off in #97774 so that the
behavior could reach projects one at a time. We have tested this
successfully internally, so the default moves back. The option stays in
place, so a build that hits a regression can set it to `false` and get
the table that a build emitted before the collapses.

The four fixtures under `test/production/app-dir/adapter-dynamic-routes`
no longer set the option, because the default now gives them the
collapsed table that their snapshots record. The snapshots themselves do
not change, since the new default produces what the explicit option
produced.

A full deploy test run with the flag on by default was already done as
part of #97738.
2026-09-04 08:30:09 +02:00
Marcos Hernanz 090f1b7275 fix: suppress missing package manager errors in next info (#97946)
## What

- capture stdout and stderr for both direct and shell-fallback
package-manager version probes
- keep successful version output unchanged and return `N/A` when a probe
fails
- add deterministic cross-platform regression coverage with a failing
Yarn shim

Closes #97932

## Why

PR #71134 added an `execSync` fallback so package-manager command shims
can be resolved on Windows. When `stdio` is omitted, Node mirrors stderr
from failed synchronous child processes to the parent, so an unavailable
Yarn or pnpm can print raw shell errors before `next info` reports
`N/A`.

Passing `{ stdio: "pipe" }` to both probes preserves captured stdout and
the Windows fallback while keeping expected probe failures silent. The
change stays scoped to package-manager version detection; shared
registry discovery is unchanged.

## Testing

- `pnpm --filter next typescript`
- `pnpm --filter next build`
- `pnpm test-types --pretty false`
- ESLint and Prettier on both changed files
- `pnpm test-start test/e2e/cli/cli.test.ts` (66 passed, 1 skipped)
- built-CLI reproduction with npm available and Yarn/pnpm absent: both
report `N/A` with no Yarn/pnpm command-not-found output
- autoreview with GPT-5.6 Sol xhigh and Claude Opus 5 xhigh: zero
findings

---------

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
2026-09-03 18:12:15 -07:00
next-js-bot[bot] 6b56068f38 v16.4.0-canary.16 v16.4.0-canary.16 2026-09-03 23:20:22 +00:00
Josh Story e82978ca63 test: remove stale deploy manifest exclusions (#98051)
## Summary

Remove two inert entries from the deploy-test manifest:

- the entry for
`test/e2e/app-dir/options-request/options-request.test.ts`, which no
longer exists
- the `test/production/standalone-mode/**/*` glob, which cannot match
the manifest's e2e-only include rule

This leaves the hard exclusion list limited to live, selectable test
suites so follow-up deploy-testing work starts from an accurate
inventory.

## Verification

- Parsed `test/deploy-tests-manifest.json` and confirmed all 26
remaining exclusions resolve to files selected by the manifest's include
rule.

<!-- NEXT_JS_LLM -->
2026-09-03 13:07:46 -07:00
Marcos Hernanz 4fffc2c653 Enable hourly next-maintainer auto-close workflow (#98225)
## Summary

- enable the existing auto-close workflow once per hour
- keep manual dispatch available
- leave the workflow logic and permissions unchanged

## Verification

- production `workflow_dispatch` succeeded end to end:
https://github.com/vercel/next.js/actions/runs/33788887152
- production returned a clean empty-queue response after OIDC
authentication and Vercel Trusted Sources validation
- Prettier and `git diff --check` pass
2026-09-03 13:03:41 -07:00
Josh Story 65c832c8b8 Return client errors for unrecognized Server Actions (#98123)
## Summary

- return `400 Bad Request` when a Server Action reference ID does not
match the expected format
- return `409 Conflict` when a well-formed action ID is unavailable in
the current deployment, including deployment skew
- apply the same classification to IDs from the `Next-Action` header and
multipart MPA forms, including direct and bound actions in Node and Edge
runtimes
- return generic text bodies matching the client error category, without
exposing action-ID details
- derive marked forwarded-action responses from the original action ID
so an unexpected or mixed-version worker response cannot change the
client error semantics

Fetch actions previously had dedicated 404 handling, while equivalent
MPA form submissions fell through to the generic error path and returned
500. This removes that transport-dependent behavior and distinguishes
malformed or scanning traffic from legitimate stale clients without
treating either case as a missing page or an internal server failure.

## Verification

- `pnpm test-dev-turbo
test/e2e/app-dir/actions-unrecognized/actions-unrecognized.test.ts`
- `pnpm test-dev-turbo test/e2e/app-dir/actions/app-action.test.ts -t
'forward.*action'`
- `pnpm test-start-turbo
test/e2e/app-dir/actions-unrecognized/actions-unrecognized.test.ts`
- `pnpm test-dev-webpack
test/e2e/app-dir/actions-unrecognized/actions-unrecognized.test.ts`
- `pnpm test-dev-turbo
test/e2e/app-dir/no-server-actions/no-server-actions.test.ts`

<!-- NEXT_JS_LLM -->
2026-09-03 10:36:53 -07:00
Marcos Hernanz 0eb4d7d585 Add Next Maintainer auto-close workflow (#98197)
## Summary

- add a manually dispatchable workflow; the hourly schedule is left
commented until initial production verification
- keep the workflow YAML small and place delivery in
`.github/scripts/next-maintainer-auto-close.js` for normal code review
- use a short-lived exact-audience GitHub Actions OIDC token with no
long-lived secret
- trust the authenticated queue contract instead of reimplementing its
Zod validation in the workflow
- post the verifier-authored comment and close with GitHub native
completed, not planned, or duplicate state reasons
- retain one invisible marker only to prevent duplicate public comments
across retries

If an issue is open with the marker, the workflow leaves it open. This
intentionally lets a human reopen win and avoids timeline
reconstruction.

## Permissions

The job grants only `contents: read`, `id-token: write`, and `issues:
write`; every other permission remains none. Both GitHub actions are
pinned to full commit SHAs. Checkout is sparse to the one trusted
JavaScript file and has credential persistence disabled.

## Verification

- mocked delivery harness passes ten scenarios: empty queue, completed,
not planned, duplicate, pull-request rejection, already-closed recovery,
independent close, open marker, transferred issue, and transient failure
- `node --check`, Prettier, and ESLint pass for the extracted
implementation
- Vercel Agent Review, Vercel Security Review, Socket Security, workflow
change detection, and documentation validation pass
- after merge, dispatch the registered workflow on `canary` and add the
production run link here before enabling the hourly schedule

## Dependency

This is the narrow GitHub write-side companion to
vercel-labs/next-maintainer-agent#541, which is deployed.
vercel-labs/next-maintainer-agent#546 further reduces the queue DTO and
changes the delivery limit to 25 first claims per rolling week; this
workflow is compatible with both DTO versions.
2026-09-03 10:32:40 -07:00
Jimmy Miller 8b9cdc5eef perf(turbopack): skip ignored files during server tracing (#97475)
So I was playing around with profiling some very simple next apps when I
noticed this. Basically, because of how things were set up we were
spending a good chunk of our build time analyzing files we were going to
reject. I went around and around trying different combinations to make
sure we ignore those files (and only the files we should ignore) to
speed up build times. Since it is a constant impact, mostly on small
projects, but on some, it can make a big difference. I had some
adversarial rounds with agents to see if they can break the config in
the ways the old comments hints at why we didn't do that. And this final
round finally couldn't get it to break.

For a v0-generated Hello World app, compile time is 28.0% faster
(+/-1.7%) and a full clean build is 15.9% faster (+/- 1.2%).

---------

Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
2026-09-03 16:16:54 +00:00
Joseph 4e2abec31d docs: note that experimental.taint taints process.env (#98145)
Enabling `experimental.taint` makes Next.js taint `process.env` for you
(`app-render.tsx`), so passing it whole to a Client Component throws.
The docs never mentioned this.

Adds it to the existing "Good to know" callout, plus a caveat that the
taint is by object reference, so `process.env.MY_VAR` and `{
...process.env }` still pass through.
2026-09-03 12:10:36 +02:00
Aurora Scharff 87a1c18eee test: preserve static params during Cache Components migration (#98209)
## Summary

Adds an agent eval covering migration of the legacy on-demand ISR
pattern: `force-static`, `revalidate`, and an empty
`generateStaticParams` result.

The assertions require the agent to enable Cache Components, preserve
the `generateStaticParams` export, and replace its empty result with at
least one parameter instead of deleting the function. The fixture also
runs the Cache Components adoption skill with the established 30-minute
timeout.

Updates the Cache Components migration guide to link each legacy route
segment config to its migration instructions. The `generateStaticParams`
section now explains that removing the export opts a dynamic route out
of ISR and links to the ISR with Cache Components guide.

## Eval results

### Before the guide change

| Treatment | Result | Duration | Behavior |
| --- | --- | ---: | --- |
| Baseline | Passed (1/1) | 482.9s | Preserved `generateStaticParams`
and returned a real seed. |
| Bundled docs / AGENTS.md | Failed (0/1) | 444.7s | Deleted
`generateStaticParams`, incorrectly treating omission as equivalent to
returning an empty array. |
| Cache Components skill | Passed (1/1) | 527.6s | Preserved
`generateStaticParams` and returned a real seed. |

The failed treatment was classified as a model failure. It read the
bundled migration guide but concluded that deleting
`generateStaticParams` preserved the defer-all-paths-to-runtime
behavior. The eval catches that loss of on-demand ISR semantics.

The full run used the identical fixture immediately before its numeric
identifier was changed from `agent-044` to the final `agent-054` name.
The fixture and assertions were unchanged.

### After the guide change

| Treatment | Result | Duration | Behavior |
| --- | --- | ---: | --- |
| Baseline | Passed (1/1) | 739.5s | Preserved `generateStaticParams`,
returned `FEATURED_EVENT_SLUG`, and compared the migrated route with the
original behavior. |
| Bundled docs / AGENTS.md | Passed (1/1) | 393.2s | Read the revised
migration and ISR guides, preserved `generateStaticParams`, and returned
`FEATURED_EVENT_SLUG`. |
| Cache Components skill | Passed (1/1) | 547.3s | Preserved
`generateStaticParams`, returned `FEATURED_EVENT_SLUG`, and verified the
build and representative routes in development. |

In the first controlled pair, the bundled-docs treatment changed from
failing to passing. Its final migration kept the route eligible for ISR,
preserved the 60-second revalidation behavior with `use cache` and
`cacheLife`, and produced a passing build.

### Additional variance check

The bundled-docs treatment was run once more against each guide version:

| Guide version | First sample | Second sample | Observed result |
| --- | --- | --- | --- |
| Before this change | Failed (444.7s) | Passed (455.6s) | 1/2 passed |
| After this change | Passed (393.2s) | Passed (425.2s) | 2/2 passed |

The second pre-change sample shows that the previous wording could still
lead the agent to the correct migration. The revised wording passed both
observed samples, suggesting that the explicit ISR consequence improves
reliability. Two samples per version are not enough to claim a stable
pass rate.

## Verification

- `pnpm --filter=next build`
- `pnpm eval agent-054-cache-components-empty-static-params --dry`
- `pnpm eval agent-054-cache-components-empty-static-params`

<!-- NEXT_JS_LLM -->
2026-09-03 11:57:49 +02:00
Tim Neutkens fb4c498852 Upgrade web-vitals to v6 and report soft navigations (#97232)
## Summary

Upgrades the vendored `web-vitals` dependency to v6.0.1 and enables
soft-navigation reporting for CLS, INP, and LCP. This lets
`useReportWebVitals` report metrics against the navigation where they
occurred, including `navigationURL` for route attribution.

The upgrade also removes the obsolete FID integration and compatibility
patch, regenerates the standard and attribution bundles, and updates the
public metric types and documentation to match the current upstream API.

Related to #97161.

## Verification

- Added production-mode coverage for initial and soft-navigation Web
Vitals reporting with both Turbopack and Webpack.

<!-- NEXT_JS_LLM -->
2026-09-03 09:45:19 +02:00
next-js-bot[bot] 83eabc8f83 Upgrade React from 21c89c9f-20260901 to f4e439e1-20260902 (#98188) 2026-09-03 07:06:44 +00:00
next-js-bot[bot] 69ec884e44 v16.4.0-canary.15 v16.4.0-canary.15 2026-09-02 23:21:09 +00:00
Luke Sandberg 5a0f57d9b9 Reduce Turbopack cache size with per-family compression (#97714)
## What?

Adds per-family compression configuration to `turbo-persistence` and
configures the Turbopack filesystem cache according to each keyspace's
access pattern:

| Family | Compression | Reason |
| --- | --- | --- |
| Infra | LZ4 HC level 4 | Preserve LZ4 decode performance while
improving write-time compression |
| TaskMeta | LZ4 HC level 4 | Preserve latency-sensitive metadata reads
|
| TaskCache | LZ4 HC level 4 | Preserve latency-sensitive task-cache
reads |
| TaskData | zstd level 3 | Prioritize the dominant disk-size
opportunity |

The low-level configuration exposes only the three presets this database
needs: LZ4, LZ4 HC4, and zstd3. Compression is applied consistently to
SST blocks, blob values, and compaction output. Each database owns a
`ThreadLocal` that lazily creates one lock-free zstd decompressor per
participating thread; the linked zstd reports each context as about 96
KiB, and all contexts are released when the database drops.

Each meta file stores its family's length-prefixed, bincode-encoded
compression preset once. SST and blob headers remain unchanged,
including the 12.5% minimum-savings fallback to uncompressed blocks. On
normal database open, the stored marker must match the runtime
`FamilyConfig`; a mismatch, invalid/truncated preset, or pre-marker meta
file is rejected before reads are served. This intentionally requires a
fresh/versioned cache after a codec change, consistent with
`turbo-persistence`'s existing no-cross-version-compatibility policy.

The marker also removes the duplicated keyspace-to-codec mapping from
`sst_inspect`: it reads the codec directly from metadata.
`turbo-persistence-tools` can inspect metadata without supplying a
Turbopack-specific family configuration.

## Why?

LZ4 decode performance is important for cache query latency, but using
one codec for every keyspace leaves a substantial disk-size opportunity
in TaskData. The families do not have the same tradeoff: metadata and
task-cache reads should stay on the LZ4 decoder, while TaskData can
spend modestly more CPU to reduce persistent cache size.

The final three-workload A/B reduced fresh cache directories by **20.61%
overall**, with no measured cold/write or warm/read regression (the
observed -3.42% / -2.39% should be treated as noise, not a speed claim).

## How?

### Real Next.js cache A/B

The complete A/B was re-run after the final fixed-preset,
bincode-marker, and database-owned thread-local revision. Both the
unchanged base worktree and final proposed working tree were
bootstrapped with `pnpm install --frozen-lockfile` and `pnpm build-all`,
using locally compiled native bindings on the same 8-vCPU Intel Xeon VM
(16.3 GiB RAM, Linux 6.18.40, Node 24.14.1). Each workload used three
fresh-cache builds and five warm-cache rebuilds; the table reports
medians.

```sh
TURBO_ENGINE_IGNORE_DIRTY=1 NEXT_TELEMETRY_DISABLED=1 \
  node packages/next/dist/bin/next build <fixture> --turbopack
```

`test/e2e/filesystem-cache` additionally used `ENABLE_CACHING=1`. The
other fixtures temporarily enabled
`experimental.turbopackFileSystemCacheForBuild`; those fixture changes
are not in this PR.

| Workload | Cache size | Cold/write | Warm/read |
| --- | ---: | ---:| ---: |
| `test/e2e/filesystem-cache` | **-20.62%** | -2.24% | -3.00% |
| `test/e2e/app-dir/app-rendering` | **-20.11%** | -6.04% | -1.47% |
| `test/e2e/app-dir/client-reference-chunking` | **-21.02%** | -1.45% |
-3.56% |
| Combined medians | **-20.61%** (96.810 → 76.862 MiB) | **-3.42%** |
**-2.39%** |

Per-family inspection of a final filesystem-cache build, using the
marker-derived codec:

| Family | Total file size | Value-block savings |
| --- | ---: | ---: |
| Infra | 52 B | n/a |
| TaskMeta | 6.31 MB | 29% |
| TaskData | 18.17 MB | 66% |
| TaskCache | 1.65 MB | n/a |

The originally planned `app` and `app-static` fixtures could not run as
standalone builds: `app` hit the existing Edge-runtime `process.cwd()`
build failure, and `app-static` required an external data endpoint
during page-data collection. They were replaced with the two successful
repository fixtures shown above under the same base/proposed
methodology.

### Temporary crate benchmark instrumentation

The codec-specific Criterion cases were used locally to gather receipts,
then removed from the delivered patch per review.

| Codec | Synthetic write | vs LZ4 | Uncached get | Cached get | DB size
|
| --- | ---: | ---: | ---: | ---: | ---: |
| LZ4 | 38.173 ms | baseline | 7.3004 µs | 10.735 µs | 21.59 MiB |
| LZ4 HC4 | 256.31 ms | **+571.7%** | 3.4853 µs | 8.5467 µs | 21.57 MiB
|
| zstd3 | 57.790 ms | **+51.4%** | 6.2460 µs | 9.5323 µs | 21.49 MiB |

The synthetic workload applies one codec to the entire database and
barely separates codecs by size, so it is not representative of the real
TaskData distribution. The all-HC write regression is real but confined
to smaller families in the actual configuration. Cached reads do not
decompress blocks, and the read samples contain substantial
ordering/outlier noise. The real-cache A/B is the read-regression guard.

## Vercel Site

### Size

2.9G	canary
2.2G	this PR

a 25% savings

### Performance

Cold build (n=5), warm build (n=3), medians:

| Scenario | Metric | Canary | Compression | Δ |
|---|---|---|---|---|
| **Cold** | wall (s) | 71.28 | 72.86 | +2.2% |
| | user (s) | 555.21 | 570.23 | +2.7% |
| | sys (s) | 94.49 | 95.00 | +0.5% |
| | maxRSS (GB) | 17.95 | 17.98 | +0.2% |
| **Warm** | wall (s) | 15.00 | 15.32 | +2.1% |
| | user (s) | 7.59 | 9.98 | +31% |
| | sys (s) | 12.79 | 12.87 | +0.6% |
| | maxRSS (GB) | 3.78 | 3.86 | +2.0% |

maxRSS converted at 1 GB = 2^30 bytes.

Looking at tracing data i see in a cold build `persist` span went from
5.06s duration using 63s cpu time to 6.04s using 73s cpu time, a ~20%
regression. Which explains the cpu regression and the time progression.

Because the time regression is in the `persist` span during shutdown the
added latency is somewhat hidden.

Of course we also see a small wall/user time regression in warm builds
due to the extra decompression costs for zstd.

### Verification

- `cargo test -p turbo-persistence` (79 tests)
- `cargo test -p turbo-tasks-backend` (99 unit tests plus integration
suites)
- `cargo test -p turbo-persistence-tools`
- `cargo test -p turbo-persistence --all-targets --no-run`
- `cargo fmt --all -- --check`
- `cargo clippy -p turbo-persistence -p turbo-persistence-tools -p
turbo-tasks-backend --all-targets -- -D warnings`
- `cargo check -p turbo-persistence -p turbo-persistence-tools -p
turbo-tasks-backend`
- `pnpm lint-ast-grep`
- `pnpm build-all`

<!-- NEXT_JS_LLM -->


<!-- fleet 621238c3-5a53-431b-8661-8c5b2920d55d -->

---------

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-02 21:51:00 +00:00
Sebastian "Sebbie" Silbermann 5b8c866c32 [test] Skip hooks of suites skipped by a lazy @force-gate (#98169)
A `describe` gated by a lazy `// @force-gate` (a condition read off the
fixture's resolved config, like `cacheComponents`) cannot be skipped at
collection time, so the gate runtime force-passes its tests at runtime
and `nextTestSetup` skips the fixture build. Hooks registered inside
such a `describe` still ran, because only the `it`/`test` globals were
wrapped. A hook that touches the `next` instance (for example a
`beforeEach` that reads `next.cliOutput`) then failed with `next
instance is not initialized yet` and failed the supposedly skipped
suite.

The gate runtime now wraps the
`beforeAll`/`afterAll`/`beforeEach`/`afterEach` globals as well and
skips a hook registered under a lazy `@force-gate` when that gate
evaluates false against the fixture's resolved config. Only a false lazy
force-gate skips a hook: an inverted `@gate` still runs its tests and
therefore needs its hooks, and a static `@force-gate` already skips the
whole `describe` at collection time. The hooks `nextTestSetup` registers
itself are marked with the new `ungatedHook` helper so that the skip
decision and the fixture cleanup still run.

The `cacheComponents disabled, edge app router` describe in
`test/e2e/cache-handlers-upstream-wiring` is converted from the
`process.env.__NEXT_CACHE_COMPONENTS` `describe.skip` ternary to `//
@force-gate !cacheComponents`, replacing a fake-green skip and
exercising the fixed path. The `!deploy` conversion of the remaining
describes is left to the branch that introduces the `deploy` condition.

Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
2026-09-02 19:45:27 +02:00
Benjamin Woodruff ‮ 5cca033c41 docs: local development: Rewrite docker section, add Windows Dev Drive and WSL2 instructions (#98164)
Follow-up to
https://vercel.slack.com/archives/C046HAU4H7F/p1787344658655399

Summary:
- Move system configuration changes into it's own section, separate from
application and project configuration changes.
- The Docker section was very light on details and didn't do a good job
of explaining why this problem happens.
- Also suggest
https://docs.docker.com/desktop/features/synchronized-file-sharing/ for
Docker and explicitly discourage `watchOptions.pollIntervalMs` as a
workaround.
- Suggest Windows Dev Drive
(https://learn.microsoft.com/en-us/windows/dev-drive/). I don't have
experience with it, but Microsoft recommends it and claims it improves
filesystem performance.
- Recommend trying WSL 2.
2026-09-02 08:49:10 -07:00
Niklas Mischkulnig 538f3f68e9 Bump swc to v77 (#97407)
There have been breaking changes to the AST:
- `BlockStmtOrExpr` is now `ArrowFunctionBody`
- function bodies are `FunctionBody` instead of `BlockStmt`
- JSX text literals are `Wtf8Atom`, instead of `Atom`
2026-09-02 16:09:18 +02:00
next-js-bot[bot] ec847d8216 Upgrade React from ff7445e6-20260831 to 21c89c9f-20260901 (#98147) 2026-09-02 13:56:20 +02:00
Luke Sandberg 8ea76d64ca turbo-tasks-backend: parent_count-driven garbage collection (#97282)
Adds garbage collection, built on the `parent_count` reference counting

### How collection works

* we scan all storage shards for collectible tasks to seed the sweep
* For each collectible task we
    1. mark it as deleted
    2. remove all outgoing edges
    3. queue new tasks for deletion if the edge removals triggered it

For removing edges we rely on the existing `CleanupOldEdges` operation,
though it is enhanced to collect the tasks that become collectible as
edges are removed

Deletion markers are transformed into tombstones by persistence and then
dropped from memory by eviction.

### Coordinator changes

GC mutates the graph, so we exclude other tasks from running while it
works. When gc completes we hand off to persistence which switches to
the existing copy on write mode.

The subtle piece is `GcPhase::into_snapshot`: GC has to hand its
exclusion directly to the snapshot that tombstones what it collected.
Swapping the flags under one lock leaves no window where an operation
could start and resurrect a just-collected task.

### Enabling it

Off by default; `TURBO_ENGINE_GC` turns it on, also requires eviction to
be enabled (otherwise deleted tasks persist in RAM)

`prevent_gc()` becomes real, pinning the calling task through
`transient_ref_count`.
2026-09-01 22:30:32 -07:00
Luke Sandberg bcfa15d0ed Remove the worker thread for turbopack builds (#96862)
Avoid spawning a worker thread to run turbopack

The typical arguments for worker threads are that they isolate the JS
heap and enable parallelism, but turbopack does both these things by
default (native heap is isolated, the js deps are minor and have a lot
of overlap with existing next server deps).

There is also no substantial parallelism opportunity from worker threads
that aren't immediately delivered by turbopacks execution model

## Benchmarks

Measured with a paired A/B harness that alternates `head` and `base`
builds so thermal drift and background load cancel out, discarding a
warmup build per variant and clearing `.next` before each run. Both
variants are the same commit except for this change. 35 paired
iterations on a default `create-next-app` (App Router, TS), Node
20.11.1, Apple silicon.

| Metric | this PR | canary | Diff | 95% CI | Wins |
| --- | --- | --- | --- | --- | --- |
| `next-build` total | 2478 ms | 2595 ms | **-117 ms (4.5%)** | [50,
183] | 30/35 |
| `run-turbopack` | 1228 ms | 1335 ms | -107 ms (8.0%) | [62, 152] |
31/35 |
| overhead (total - turbopack) | 1250 ms | 1260 ms | -10 ms (0.8%) |
[-27, 46] | 20/35 |
| wall clock | 2775 ms | 2888 ms | -112 ms (3.9%) | [45, 180] | 30/35 |
| peak RSS | 1175 MiB | 1195 MiB | **-19 MiB (1.6%)** | [3.4, 35.3] |
22/35 |

Significant by both a paired t-test and a Wilcoxon signed-rank test (p =
0.0001 for total build time, p = 0.025 for RSS). The Wilcoxon is
included because a couple of slow builds per run would otherwise
dominate the variance.

Nearly all of the saving lands inside the `run-turbopack` span rather
than around it, which is expected: that span wraps the worker spawn, the
IPC to marshal the build across, and the teardown, so deleting the
worker removes work from inside it.

The win is proportionally smaller on larger apps, where compilation
dominates. On `bench/basic-app` (~400 source modules, ~19s builds) the
same ~115 ms is well inside the noise -- 12 paired iterations there
showed no significant difference on any metric. So this is a real but
small improvement that is only user-visible on small builds:

| Metric | this PR | canary | Diff | 95% CI |
| --- | --- | --- | --- | --- |
| `next-build` total | 19313 ms | 19436 ms | -123 ms (0.6%) | [-256,
502] |
| overhead (total - turbopack) | 1424 ms | 1415 ms | +9 ms (0.6% slower)
| [-52, 70] |

Caveats: single machine and two fixture shapes. Peak RSS is the build
process's own footprint (`/usr/bin/time -l`), which includes the worker
thread's isolate but not the separately spawned static-generation
workers. A CI runner with fewer cores would plausibly show a larger
effect.
2026-09-01 22:27:43 -07:00
Luke Sandberg 483f84202d turbo-tasks-malloc: address review feedback on #97761 (#98153)
### What?

Follow-up to #97761, which was auto-submitted before @bgw's review
comments were addressed. Comment-only changes; no behavior change.

- Name the nulled-out out-params in the `mi_process_info` call, applying
the suggestion as written.
- Trim the `memory_usage` doc comment to a brief description plus a link
to the mimalloc docs. `mi_process_info` is absent from mimalloc's
published doxygen docs, so the link points at the `libmimalloc-sys`
rustdoc, which reproduces the header's table describing each figure. It
is a plain URL rather than an intra-doc link because `libmimalloc-sys`
is an optional dependency — an intra-doc link fails to resolve under
`--no-default-features`.
- Trim the test's doc comment to the single suggested line.

The `#[global_allocator]` suggestion matched the existing code exactly,
so nothing to change there. Left the `vec!` in the test alone, per "this
is fine because every test in this crate should expect this global
allocator".

While in the same doc comment, dropped the `[`self::counter`]` link,
which was raising a `private_intra_doc_links` warning.

### How?

Verified with `cargo fmt --check`, `cargo clippy -p turbo-tasks-malloc
--all-targets`, `cargo test -p turbo-tasks-malloc` (6 passed), and
`cargo doc` under `RUSTDOCFLAGS="-D warnings"` with both default and
`--no-default-features`.
2026-09-01 23:45:31 +00:00
next-js-bot[bot] b1dfa8a888 v16.4.0-canary.14 v16.4.0-canary.14 2026-09-01 23:20:10 +00:00
Luke Sandberg 4425414ad2 turbo-tasks-malloc: report memory from mimalloc (#97761)
### What?

`turbo-tasks-malloc` keeps a process-wide `ALLOCATED` atomic to answer
`memory_usage()`. To optimize performance thread local buffers were
maintained but this lead to code size issues. So instead we rely on
mimalloc apis saving 1.6M of binary size


### Why not ask the OS?

That was the obvious alternative, and it does not work well. The natural
per-platform figures are not the same quantity: macOS `phys_footprint`
and Windows `PrivateUsage` account for compressed and swapped pages, but
Linux `VmRSS` does not — so under swap or zram the Linux number can read
flat, or fall, while real consumption climbs. Closing that gap means
`/proc/self/smaps_rollup` (`Pss + SwapPss`), which is not the cheap
one-line read it looks like, it takes on the order of [100ms per
4GB](https://github.com/ncabatoff/process-exporter/issues/246), and it
would contend with other allocations in our process.

### How?

`memory_usage()` reads `current_commit` from `mi_process_info`. We fall
back to the `ALLOCATED` atomic only when `custom_allocator` is off.


### Behaviour changes

**It does not track frees in lock step.** mimalloc reuses and purges
pages on its own schedule or when eviction calls `collect`

### Size

Rust inlines `#[global_allocator]` methods into every allocation site,
so the buffer arithmetic and the branch guarding it were duplicated
across the whole binary. Dropping them from the default build is where
the win comes from.

`libnext_napi_bindings.dylib`, release profile as shipped (thin LTO,
`codegen-units = 1`).

| Section | canary | this PR | Δ |
|---|---:|---:|---:|
| `__text` | 64,833,760 | 63,319,776 | **−1,513,984** |
| `__eh_frame` | 8,966,904 | 8,748,464 | −218,440 |
| `__gcc_except_tab` | 2,234,428 | 2,229,484 | −4,944 |
| `__unwind_info` | 1,489,072 | 1,484,644 | −4,428 |
| **Total** | | | **−1,741,796 = −1.66 MiB** |

### Performance

`cargo bench -p turbo-tasks-malloc --bench allocation`, macOS aarch64,
revisions interleaved across 3 rounds so drift hits both arms equally.
Figures are the paired per-round deltas.

| benchmark | canary | this PR | mean Δ | per-round |
|---|---:|---:|---:|---|
| `alloc_dealloc` | 6.65 ns | 6.45 ns | **−2.9%** | −3.7% .. −2.4% |
| `alloc_realloc_dealloc` | 18.62 ns | 16.76 ns | **−10.0%** | −10.2% ..
−9.7% |

Removing the counter takes the buffer arithmetic and its guarding branch
out of every allocation, and a realloc pays that twice — which is
roughly the shape of the result. That said, the absolute numbers are
small (a couple of hundred picoseconds on `alloc_dealloc`) and this
benchmark is noisy, so treat the direction as the signal rather than the
magnitude.
2026-09-01 14:58:42 -07:00
Niklas Mischkulnig 9c626ac94b Remove Edge runtime handling in use-cache-wrapper (#98139)
Cache components are only compatible with Node.js runtime anyway. So
this was basically dead code. Removing it doesn't break any existing
functionality.
2026-09-01 22:59:38 +02:00
Sebastian "Sebbie" Silbermann 52cd5b96ab Skip discussions in the issue_lock workflow (#98146)
The last remaining `Lock Threads` failures are likely due to missing
permissions for discussions which `dessant/lock-threads` handles by
default.

This disables discussion handling to get the workflow green to start
tracking regressions. We can discuss if we want to lock inactive
discussions in a follow-up (which would be new behavior this workflow
never performed).

Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
2026-09-01 18:06:18 +00:00
Sebastian "Sebbie" Silbermann 3866d6fd2b [ci] Fix issue_lock (#97755)
Fixes
> "github-token" length must be less than or equal to 100 characters
long
-- https://github.com/vercel/next.js/actions/runs/33188474239

GitHub uses stateless tokens now which are way longer. See
https://github.com/dessant/lock-threads/issues/55

---------

Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
2026-09-01 17:50:10 +02:00
Aurora Scharff 1a9245c7d0 docs: document preloading with Cache Components (#97864)
## Summary

- add the preload pattern to the current App Router data fetching guide
- distinguish automatic fetch memoization, React cache for non-fetch
data, and Cache Functions
- clarify request-scoped private caching and when stale: Infinity should
not constrain the route stale time

The previous-model caching guide remains unchanged.

## Verification

- Prettier on both edited docs
- alex on both edited docs
- git diff --check
- Not run: full framework build because this is a documentation-only
change

<!-- NEXT_JS_LLM -->
2026-09-01 13:03:19 +02:00
Steven 1076c47453 chore: Remove expired sharp release age exclusions (#98120)
- PR #97931 temporarily exempted sharp@0.35.4 and its @img/* binaries to
ship same day.
  - Its now past  the 48-hour minimum release age.
  - This PR removes those temporary exemptions.
2026-09-01 09:45:41 +02:00
next-js-bot[bot] 258b1c1bc0 v16.4.0-canary.13 v16.4.0-canary.13 2026-08-31 23:21:17 +00:00
Jude Gao fb49d8b754 evals: stop requiring the removed viewTransition flag in agent-043 (#98116)
The view-transitions eval kept requiring a config option that #96098
removed, so models following the current docs were failed for correct
solutions. Evals only, no runtime change.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 18:36:10 -04:00
Aurora Scharff e9180eae2a errors: shorten "use cache" messages and unify them into one factory (#94300)
### What?

Centralizes related <code>"use cache"</code> scope errors and rewrites
them as shorter, actionable messages with consistent <code>Learn
more:</code> links. It also updates the reachable revalidation errors
for rendering and <code>generateStaticParams</code>.

### Why?

The previous messages were long, inconsistent, and sometimes missing
documentation links. Some new cache-specific revalidation messages were
also hidden by an earlier render-phase error. The new messages name the
constraint, give the immediate fix, and link to the relevant
documentation.

### How?

Adds centralized error factories and dedicated error pages for request
data, cache configuration, private-cache composition, and revalidation.
When an active App Router route is available, the messages include it.
The error pages use consistent terminology and complete examples. The
<code>unstable_cache()</code> reference now documents the
request-dependent operations that these errors reject.

### Before and after

Route-based examples use <code>/products</code>. Bracketed values are
alternatives in matching order. For example, <code>[E1482, E1486,
E1489]</code> maps to <code>[headers(), cookies(), request.url]</code>.
Each runtime error contains one alternative, but the table groups
messages with the same template.

#### Messages that include the route

| Cases | Before | After |
| --- | --- | --- |
| E1480 · <code>searchParams</code> in <code>"use cache"</code> | Route
/products used <code>searchParams</code> inside "use cache". Accessing
dynamic request data inside a cache scope is not supported. If you need
some search params inside a cached function await
<code>searchParams</code> outside of the cached function and pass only
the required search params as arguments to the cached function. See more
info here: https://nextjs.org/docs/messages/next-request-in-use-cache |
Route "/products": <code>searchParams</code> can't be read inside
<code>"use cache"</code>. Await it outside the cached function and pass
what you need as an argument.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| [E1482, E1486, E1489] · [<code>headers()</code>,
<code>cookies()</code>, <code>request.url</code>] in <code>"use
cache"</code> | Route /products used [<code>headers()</code>,
<code>cookies()</code>, <code>request.url</code>] inside "use cache".
Accessing Dynamic data sources inside a cache scope is not supported. If
you need this data inside a cached function use [<code>headers()</code>,
<code>cookies()</code>, <code>request.url</code>] outside of the cached
function and pass the required dynamic data in as an argument. See more
info here: https://nextjs.org/docs/messages/next-request-in-use-cache |
Route "/products": [<code>headers()</code>, <code>cookies()</code>,
<code>request.url</code>] can't be read inside <code>"use cache"</code>.
Read it outside the cached function and pass what you need as an
argument.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| [E1481, E1485, E1492] · [<code>cookies()</code>,
<code>request.url</code>, <code>headers()</code>] in
<code>unstable_cache()</code> | Route /products used
[<code>cookies()</code>, <code>request.url</code>,
<code>headers()</code>] inside a function cached with
<code>unstable_cache()</code>. Accessing Dynamic data sources inside a
cache scope is not supported. If you need this data inside a cached
function use [<code>cookies()</code>, <code>request.url</code>,
<code>headers()</code>] outside of the cached function and pass the
required dynamic data in as an argument. See more info here:
https://nextjs.org/docs/app/api-reference/functions/unstable_cache |
Route "/products": [<code>cookies()</code>, <code>request.url</code>,
<code>headers()</code>] can't be read inside
<code>unstable_cache()</code>. Read it outside the cached function and
pass what you need as an argument.<br><br>Learn more:
https://nextjs.org/docs/app/api-reference/functions/unstable_cache |
| [E1484, E1491] · <code>draftMode().enable()</code> in
[<code>unstable_cache()</code>, <code>"use cache"</code>] | Route
/products used "draftMode().enable()" inside [a function cached with
<code>unstable_cache()</code>, "use cache"]. The enabled status of
<code>draftMode()</code> can be read in caches but you must not enable
or disable <code>draftMode()</code> inside a cache. See more info here:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] | Route
"/products": <code>draftMode().enable()</code> can't be called inside
[<code>unstable_cache()</code>, <code>"use cache"</code>]. Draft mode
can be read inside a cached function, but enabling or disabling it must
happen outside.<br><br>Learn more:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] |
| [E1488, E1499] · <code>connection()</code> in
[<code>unstable_cache()</code>, <code>"use cache"</code>] | Route
/products used <code>connection()</code> inside [a function cached with
<code>unstable_cache()</code>, "use cache"]. The
<code>connection()</code> function is used to indicate the subsequent
code must only run when there is an actual request, but caches must be
able to be produced before a request, so this function is not allowed in
this scope. See more info here:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] | Route
"/products": <code>connection()</code> can't be called inside
[<code>unstable_cache()</code>, <code>"use cache"</code>] because cached
functions may run during prerendering, without an incoming request. Call
it outside the cached function.<br><br>Learn more:
[https://nextjs.org/docs/app/api-reference/functions/unstable_cache,
https://nextjs.org/docs/messages/next-request-in-use-cache] |
| E1494 · <code>connection()</code> in <code>"use cache: private"</code>
| Route /products used <code>connection()</code> inside "use cache:
private". The <code>connection()</code> function is used to indicate the
subsequent code must only run when there is an actual navigation
request, but caches must be able to be produced before a navigation
request, so this function is not allowed in this scope. See more info
here: https://nextjs.org/docs/messages/next-request-in-use-cache | Route
"/products": <code>connection()</code> can't be called inside <code>"use
cache: private"</code> because private cached functions may run during
prefetching, without a navigation request. Call it outside the cached
function.<br><br>Learn more:
https://nextjs.org/docs/app/api-reference/directives/use-cache-private |
| [E1483, E1495] · <code>revalidateTag("products")</code> in [<code>"use
cache"</code>, <code>unstable_cache()</code>] | Route /products used
"revalidateTag products" inside [a "use cache", a function cached with
"unstable_cache(...)"] which is unsupported. To ensure revalidation is
performed consistently it must always happen outside of renders and
cached functions. See more info here:
https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
| Route "/products": <code>revalidateTag("products")</code> can't be
called during render, inside a cached function, or inside
<code>generateStaticParams</code>. Call it from a Server Action or Route
Handler instead.<br><br>Learn more:
https://nextjs.org/docs/messages/revalidate-in-use-cache |
| During render | Route /products used "revalidateTag products" during
render which is unsupported. To ensure revalidation is performed
consistently it must always happen outside of renders and cached
functions. See more info here:
https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
| Route "/products": <code>revalidateTag("products")</code> can't be
called during render, inside a cached function, or inside
<code>generateStaticParams</code>. Call it from a Server Action or Route
Handler instead.<br><br>Learn more:
https://nextjs.org/docs/messages/revalidate-in-use-cache |
| Inside <code>generateStaticParams</code> | Route /products used
"revalidateTag products" inside <code>generateStaticParams</code> which
is unsupported. To ensure revalidation is performed consistently it must
always happen outside of renders and cached functions. See more info
here:
https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering
| Route "/products": <code>revalidateTag("products")</code> can't be
called inside <code>generateStaticParams</code>. Call it from a Server
Action or Route Handler instead.<br><br>Learn more:
https://nextjs.org/docs/messages/revalidate-in-use-cache |

#### Other messages

| Cases | Before | After |
| --- | --- | --- |
| [E1477, E1490] · Nested cache with [short <code>expire</code>,
<code>revalidate: 0</code>] | A "use cache" with [short
<code>expire</code> (under 5 minutes), zero <code>revalidate</code>] is
nested inside another "use cache" that has no explicit
<code>cacheLife</code>, which is not allowed during prerendering. Add
<code>cacheLife()</code> to the outer "use cache" to choose whether it
should be prerendered [with longer <code>expire</code>, with non-zero
<code>revalidate</code>] or remain dynamic [with short
<code>expire</code>, with zero <code>revalidate</code>]. Read more:
https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife
| Route <code>"/products"</code>: A nested <code>"use cache"</code> with
[a short <code>expire</code> (under 5 minutes), <code>revalidate:
0</code>] is inside an outer <code>"use cache"</code> that has no
<code>cacheLife()</code>. Add <code>cacheLife()</code> to the outer one
to choose whether to prerender it [with a longer <code>expire</code>,
with a non-zero <code>revalidate</code>] or keep it dynamic [with a
short <code>expire</code>, with <code>revalidate:
0</code>].<br><br>Learn more:
https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife
|
| E1478 · External promise | Filling a "use cache" entry appears to be
stuck on shared state from the outer render scope. The same function
completed when run in isolation, which usually means a module-scoped
value (for example a top-level Map used to dedupe fetches) is joining a
promise created outside the cache. "use cache" already dedupes calls
with the same arguments within a request and across requests on the same
server instance, so the surrounding dedupe layer is both unnecessary and
the likely cause. Remove it and rely on "use cache" alone for
deduping.<br><br><em>No documentation link.</em> | Route
<code>"/products"</code>: A <code>"use cache"</code> function is
awaiting a promise created outside it. The same call completed when run
in isolation, so a module-scoped value (often a top-level
<code>Map</code> used to dedupe fetches) is most likely blocking it.
<code>"use cache"</code> already dedupes calls with the same arguments.
Remove the surrounding dedupe layer.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| [E1479, E1498] · [<code>cacheTag()</code>, <code>cacheLife()</code>]
outside a cached function | [<code>cacheTag()</code>,
<code>cacheLife()</code>] can only be called inside a "use cache"
function.<br><br><em>No documentation link.</em> | Route
<code>"/products"</code>: [<code>cacheTag()</code>,
<code>cacheLife()</code>] can only be called inside a <code>"use
cache"</code> or <code>"use cache: private"</code>
function.<br><br>Learn more:
[https://nextjs.org/docs/messages/cache-tag-outside-use-cache,
https://nextjs.org/docs/messages/cache-life-outside-use-cache]<br><br>Outside
an App Router route, the same message is shown without the route prefix.
|
| E1487 · Prerender timeout | Filling a cache during prerender timed
out, likely because request-specific arguments such as params,
searchParams, cookies() or dynamic data were used inside "use
cache".<br><br><em>No documentation link.</em> | Route
<code>"/products"</code>: A <code>"use cache"</code> function took too
long during prerendering. The most common cause is passing unresolved
request-specific arguments, such as <code>params</code> or
<code>searchParams</code>, into the cached function. Resolve the data
before calling the function and pass only the values you
need.<br><br>Learn more:
https://nextjs.org/docs/messages/next-request-in-use-cache |
| E1493 · Private cache inside a public cache | "use cache: private"
must not be used within "use cache". It can only be nested inside of
another "use cache: private".<br><br><em>No documentation link.</em> |
Route <code>"/products"</code>: <code>"use cache: private"</code> can't
be nested inside <code>"use cache"</code> because a shared cached
function can't depend on private request data. Nest it only inside
another <code>"use cache: private"</code>.<br><br>Learn more:
https://nextjs.org/docs/messages/use-cache-private-composition |
| E1496 · Private cache without a request | "use cache: private" cannot
be used outside of a request context.<br><br><em>No documentation
link.</em> | Route <code>"/products"</code>: <code>"use cache:
private"</code> needs an active request, so it can't be used during
<code>generateStaticParams</code> or other build-time contexts. Move it
to a request-time component or function.<br><br>Learn more:
https://nextjs.org/docs/messages/use-cache-private-composition |
| E1497 · Private cache inside <code>unstable_cache()</code> | "use
cache: private" must not be used within
<code>unstable_cache()</code>.<br><br><em>No documentation link.</em> |
Route <code>"/products"</code>: <code>"use cache: private"</code> can't
be used inside <code>unstable_cache()</code> because
<code>unstable_cache()</code> uses a shared cache that can't contain
private request data. Call the private cached function outside
<code>unstable_cache()</code>.<br><br>Learn more:
https://nextjs.org/docs/messages/use-cache-private-composition |

### Runtime verification

- Exercised the 13 route-based message IDs through minimal dev-runtime
reproductions. Twelve reached the new factories from userland, with
byte-identical output across three requests each.
- The `request.url` error for `"use cache"` Route Handlers is currently
limited to the prerender path. A dynamic request captured by a cached
closure can bypass that tracking; this is existing framework behavior to
follow up separately.
- Passing the `searchParams` promise into a nested cached function can
still surface the earlier synchronous dynamic-API error before this
factory. The new message is verified when the cache scope reads its own
`searchParams` value.
- A follow-up preview verification triggered 17 of the 19 rewritten
messages from userland with byte-identical output. The timeout-driven
external-promise and prerender-timeout messages were verified in source
and through their focused test coverage.

### Verification

- <code>CI=1 pnpm build-all</code>
- <code>pnpm --filter=next types</code>
- <code>pnpm --filter=next build</code>
- <code>HEADLESS=true pnpm test-dev-turbo
test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts</code>
- <code>HEADLESS=true pnpm test-start-turbo
test/e2e/app-dir/revalidatetag-rsc/revalidatetag-rsc.test.ts</code>
- <code>NEXT_SKIP_ISOLATE=1 HEADLESS=true pnpm test-dev-webpack
test/e2e/app-dir/cache-components-errors/use-cache.test.ts -t 'cacheLife
with (expire &lt; 5 minutes|revalidate: 0)'</code> (6 tests and 6
snapshots passed)
- <code>pnpm test-dev
test/e2e/app-dir/cache-components-errors/use-cache.test.ts --projects
jest.config.*</code> (42 tests and 42 snapshots passed across Turbopack
and webpack)
- <code>pnpm test-dev
test/e2e/app-dir/use-cache-hanging/use-cache-hanging.test.ts --projects
jest.config.*</code> (10 tests and 8 snapshots passed across Turbopack
and webpack)
- <code>pnpm test-dev
test/e2e/app-dir/use-cache-configured-timeout/use-cache-configured-timeout.test.ts
--projects jest.config.*</code> (4 tests and 2 snapshots passed across
Turbopack and webpack)
- Prettier, ESLint, and Alex on the changed source, tests, and error
pages

<!-- NEXT_JS_LLM -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 00:29:56 +02:00
Aurora Scharff d434afa837 test: add adoption skill evals (#97759)
## Summary

- add end-to-end adoption evals for Partial Prefetching and Cache
Components
- compare the baseline, bundled docs, and local adoption skill
independently
- support local skill installation and longer timeouts for
browser-backed workflows

## Results

One `claude-opus-4-8` run was retained per treatment and adoption eval,
judged by `claude-haiku-4-5`. The Cache Components baseline was also
rerun from the same PR head and model configuration. These results are
directional rather than statistically stable.

| Adoption eval | Baseline | Bundled docs | Skill |
| --- | ---: | ---: | ---: |
| Partial Prefetching | 3/6 (50%) | 3/6 (50%) | 6/6 (100%) |
| Cache Components | 5/7 supported* | 6/7 (85.7%) | 7/7 (100%) |

The Partial Prefetching skill was the only treatment to establish a
passing legacy `instant()` baseline before migration, rerun the same
production Playwright assertions after enabling Partial Prefetching, and
preserve the selected eager-link contract. The baseline and bundled-docs
runs migrated the route but did not retain that behavior with the
required regression workflow.

### Cache Components baseline audit

The clean Cache Components baseline rerun completed in 682 seconds. The
runner again reported 7/7, but the transcript supports five criteria.
Two agentic checks were false positives:

- The product route rendered only `<main>` and a `<Suspense
fallback={null}>` outside the URL-specific work, so it did not preserve
useful visible shell content.
- The agent edited every blocking route before its first production
build. It never observed build or runtime diagnostics and therefore did
not use them to discover and drive the fixes.

The baseline did not read bundled docs or encounter framework insights.
It inspected the installed Next.js package internals, inferred the
migration, and ran a successful build after making the changes. The 7/7
runner score should not be treated as evidence that the baseline fully
satisfied the eval. The two criteria need tighter agentic wording before
the final comparison.

The current bundled-docs and skill scores remain 6/7 and 7/7. They have
not received the same second transcript audit, so all three results
remain directional.

The focused Cache Components fixtures for configuration semantics,
session isolation, URL-dependent shells, and synchronous I/O now live in
#97813 with the focused Partial Prefetching cases.

## Scope

These are PR-local experiments for developing and reviewing the docs and
skills. This PR does not register them in the public `evals.nextjs.org`
suite, which is configured separately in the OSS eval repository.

## Validation

- `pnpm exec tsc -p evals/tsconfig.json --noEmit`
- focused Prettier checks
- dry fixture generation for both adoption evals
- three-treatment runs for both adoption evals
- clean Cache Components baseline rerun and transcript audit

<!-- NEXT_JS_LLM -->
2026-08-31 20:52:36 +02:00
next-js-bot[bot] 9db9c97e6f Upgrade React from 29d9d318-20260826 to ff7445e6-20260831 (#98113) 2026-08-31 18:45:16 +00:00
Sebastian "Sebbie" Silbermann 429514a468 [ci] Simplify upload-preview-tarballs workflow (#98109)
The upload script dropped its `@vercel/blob` usage in #97922 and
[imports only built-in modules
today](https://github.com/vercel/next.js/blob/canary/scripts/upload-preview-tarballs.js),
so the job no longer needs node_modules at all which allows simplifying
the workflow.


Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
2026-08-31 20:20:10 +02:00
Will Binns-Smith 2c752da34d devlow: use commander for cli argument parsing (#93860)
This has devlow use `commander` to parse its arguments.

There's a breaking change here to make it fit better into `commander`'s
patterns without extra code. It also makes things more explicit:

Arbitrary variant filtering can no longer be done with
`--variantname=value`. Now that there are more flags, these share the
namespace with these, so instead we use `--filter variantname=value`, or
`-F variantname=value`.

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com>
2026-08-31 10:56:56 -07:00
Janka Uryga adba1e3f2a [PPF] Pass in original searchParams for private-cache pages (#98041)
The handing for page components marked with `use cache: private` was
assuming that searchParams can never hang in `prerender-runtime`, so it
passed the serialized `innerSearchParams` to the page, which we expected
to just be a resolved promise. This *used to* be correct when runtime
prerenders always had search params available, because the serialized
search params were always equivalent to `outerSearchParams`.

However, for `partialPrefetching` we started using `prerender-runtime`
for runtime shells, where search params are not available, and if a
private cache awaits them, it needs to abort filling and become dynamic,
same as when it awaits params. So we need to trigger
`dynamicAccessAbortController` when `searchParams` is awaited:

```tsx
export default async function Page({ searchParams }) {
  'use cache: private'
  // this should trigger `dynamicAccessAbortController.abort()`, so we need the original instrumented
  // searchParams object instead of the serialized one
  await searchParams
}
```

This is fixed by using the outer searchParams object in the
`isPageSegmentFunction` codepath of `use-cache-wrapper` -- we're
preserving the instrumented promise, so the cache prerender will abort
as expected.
(before this fix, a shell prefetch of such a page would get a
deserialized hanging promise for `searchParams` and hang until it times
out)

2 of the 3 newly added tests ("params in a public cache" and "params in
a private cache") were already passing, because `params` was already
being preserved, but i'm adding them here to prevent regressions. The
"search params in a private cache" one was failing due to the cache
timing out, and is now passing. I'm not covering "search params in
public cache" because that's an error and is already tested elsewhere.
2026-08-31 19:15:43 +02:00
Joseph b48cb74271 docs: document create-next-app Cache Components prompt and flag (#97798)
Follow up to: Add Cache Components option to create-next-app - #97695
2026-08-31 19:06:39 +02:00
Will Binns-Smith ec107c16dd lazy server hmr (#96566)
## Summary

Make Turbopack server HMR demand-driven. Server updates are now compiled
and applied when the next relevant request writes an endpoint, instead
of eagerly evaluating changed server modules after every file change.

This replaces the aggregate server HMR subscription with an on-demand
update API, while preserving incremental updates and falling back to
full cache eviction when a restart is required.

Test Plan: added an e2e test

<!-- NEXT_JS_LLM -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com>
2026-08-31 09:12:51 -07:00
Will Binns-Smith 724641c6a2 Fix Turbopack HMR recovery after dropped connections (#97966)
### What?

Track the latest applied Turbopack HMR 'hash' in the browser runtime and
include it when subscriptions are restored after reconnecting. Reload
the page when the client's hash differs from the server's current hash.

Add an App Router development test that interrupts HMR traffic, misses
an update, restores the connection, and verifies the page reloads into
the current revision.

### Why?

A browser that temporarily loses its HMR connection can miss updates and
remain out of sync after reconnecting.

### How?

Attach the current HMR hash to Turbopack connection and update messages.
The runtime records the last hash it processed and sends it with each
subscription. The development server compares that value against its
current hash and requests a full reload on mismatch.

<!-- NEXT_JS_LLM -->
2026-08-31 09:06:58 -07:00
Niklas Mischkulnig 8330e4c4cd test: Improve test cache handler implementations (#98098)
- Some of them were not forwarding `getExpiration` or `softTags` to
`defaultCacheHandler`
- use-cache-cross-deployment was ignoring softTags and expiration. (This
patch was written by Sol)

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2026-08-31 13:59:14 +02:00
Hendrik Liebau 00b2275c26 docs: Fix the documented contract for custom cache handlers (#98039)
The documented in-memory handler stored the resolved entry and returned
it from every `get`. A stored stream keeps the request that produced the
entry reachable. The second cache hit also fails, because it reads a
stream that another reader already consumed. The render then reports
"Invalid state: ReadableStream is locked".

The "Handling Streams" section described a second, milder mistake. It
told a handler to tee the stored stream in `get`. One branch goes back
into the store, and the other serves the current read. That pattern
serves every hit correctly. The built-in handler used it until #97941,
so the page described the framework's own implementation. The stored
branch retains the same request, and it also holds the source stream and
its buffered chunks. The size a store accounts for covers none of that.
The page now states that cost, because a reader who thinks of teeing
would otherwise read the rule as inapplicable.

`get` told a handler to drop an entry once it is past `revalidate`.
Next.js compares `timestamp` against `expire` on every read, and it
treats a too-old entry as a miss. It applies the same check to
`revalidate` when the result goes into another server cache. A handler
therefore needs no age check at all. The built-in handler still drops at
`revalidate`, as a deliberate policy for an in-memory cache, and the
page now presents that as a policy instead of a rule.

Something still has to reclaim entries, so the page states that eviction
belongs to the handler. Next.js never deletes from it. An age check at
serve time is no substitute, because it only reaches the keys that a
reader still asks for.

The page now follows an order in which no rule appears after the code
that depends on it. The stream rules sit in one section ahead of the API
reference. `CacheEntry Type` moves above the methods that reference it.
`set` precedes `get`, because a handler writes an entry before it reads
one. The tag and error sections move above the examples.

`types.ts` described `refreshTags` as periodic, and the page described
it as a call before each request. Neither is accurate. It runs once per
request, before the first cache read for its kind. A request that reads
nothing from a handler never calls it.

Neither `types.ts` nor the page documented the `revalidate: -1` signal,
which the built-in handler relies on. A negative value always lies in
the past, so it serves an entry with a stale tag one more time while
Next.js generates a fresh one.

`types.ts` now states these rules as well, so a handler author no longer
has to read the built-in implementation to find them. It also covers the
`pendingEntry` promise, which a handler must not retain either.

`streamFromBuffer` now records why it has to stay a default stream. A
byte stream transfers the buffer of every chunk it receives. That would
detach the buffer an in-memory handler serves many reads from. The
comment in `tiered-cache-handler.ts` about cancelling a teed stream now
says that the backing handler is user-configured, because the built-in
one no longer returns such a branch.

The handler test from #97941 now records a limit of its own. It only
fails on Node 20 and 22, whose `AsyncLocalStorage` attaches the active
store to every promise. CI runs Node 20.9, so a regression fails there.
A local run on a newer Node passes.

Nothing in this change affects runtime behavior.
2026-08-31 13:09:07 +02:00
Steven d4c838a7d4 chore: remove unused code from image optimizer (#98060)
This PR removes some unused code from the image optimizer.

- Remove redundant MIME validation
- Remove unreachable fallback error

These used to be necessary until PR
https://github.com/vercel/next.js/pull/82118 removed the fallback.

Before 82118:

```js
upstreamType = detectContentType(upstreamBuffer) || imageUpstream.contentType?.toLowerCase().trim()
```

After 82118:

```js
upstreamType = detectContentType(upstreamBuffer)
```

Now there is never a case where upstreamType is invalid since its
already been validated, there is no more fallback.
2026-08-31 12:02:20 +02:00