Commit Graph

35740 Commits

Author SHA1 Message Date
Niklas Mischkulnig 164d9c1900 Durable use cache: fix runtime env var mutation (#98558)
### Problem
1. Next computes the warmup cache key while `SOME_ENV_VAR` is unset.
2. Rendering ends up setting `SOME_ENV_VAR`.
3. Final prerender expects the cache entry to exist, but the env var
changed, so the key changed and it's a miss.
4. Unexpected miss and bailout.

This lead to
```
Error: Route "foo": Unexpected cache miss after cache warming phase during prerendering. This is
likely caused by non-deterministic arguments that differ between the cache warming phase and the
final prerender phase (e.g. unstable array order). Ensure that arguments passed to cached functions
are deterministic.

Error: Route "foo": Next.js encountered uncached or runtime data during
prerendering.
```

### Solution

~~Instead, snapshot the env vars once at module evaluation time of the
`use cache` function. This ensures that the cache key doesn't change
over time. This is also how it worked thus far (with deployment id as
the cache key): changing the env var over the lifetime of the process
didn't lead to a reexecution of the `use cache` function~~

RDC already stores cache entries generated in the previous phase (be it
during `next build` multi-phase rendering, or from prerender->resuming
at runtime). Root params are already excluded from the cache keys when
storing in RDC. Also exclude the env var hash bit, to conform to this
system of preventing tearing (at the cost of potential staleness).
2026-09-14 11:32:24 +02:00
Niklas Mischkulnig 729ff712b7 refactor(turbopack): drop the generic on new_atom, and stop it copying an owned string (#98520)
### What?

Follow-up to the review comments on #98497.

`new_atom` loses its generic parameter and takes a `Cow<'_, str>`, which
as a side effect stops it
copying a string it was handed ownership of. `FileSystemPath`'s
`get_relative_request_to` stops
hand-rolling a conversion the standard `From` impl already does, and the
two tests added for it in
`path.rs` are replaced by one that covers what that layer actually adds.

### Why?

@bgw noted that `get_relative_request_to` was matching on the `Cow` it
gets back only to call
`.into()` in both arms, and wondered whether `impl From<Cow<'_, str>>
for RcStr` was missing a fast
path for the owned case.

It was not missing one, and it never needed one — but an owned string
*was* being copied, for a
different reason. `new_atom` was generic over `T: AsRef<str> +
Into<String>` and opened with
`let text = text.as_ref();`, which shadows the owned value. From that
point on only a `&str` was in
scope, so building the `Box<str>` allocated a fresh buffer and copied
into it, dropping the `String`
that a `Cow::Owned` had just handed over.

So this is not a fast path being added. It is an accidental
pessimisation being removed: the generic
plus the `as_ref()` were exactly what stopped the standard conversion
from doing the right thing on
its own. Whether that copy ever mattered in practice is a separate
question, and one this does not
attempt to answer.

The other two comments were about the tests: they re-ran the case table
that already lives in
`turbo-unix-path`, which is where the path computation itself is
implemented and tested — "60 LOC to
test a 3 LOC branch".

### How?

`new_atom` takes a `Cow<'_, str>`. All five of its callers already
passed one, so nothing else
changes, and ownership now survives to where the `Box<str>` is built —
`Box<str>`'s own
`From<Cow<'_, str>>` moves an owned buffer in and copies only a borrowed
one. The inline-atom path is
untouched, since it reads through a `&str` either way and does not
allocate.

Contents-based assertions cannot distinguish a moved buffer from a
copied one, so the test compares
the data pointer before and after the conversion. It uses a string
longer than `MAX_INLINE_LEN`,
because an inline atom stores its bytes in the tagged value by design
and could never preserve a
pointer, and it asserts the atom really is dynamic so it cannot pass
vacuously if that boundary ever
moves. It fails on `canary` today.

`get_relative_request_to` collapses to a single `.into()`. Its sibling
`get_relative_path_to` keeps
its match on purpose: one arm uses `std::ptr::eq` to hand back a clone
of the `RcStr` the path
already holds, which the blanket conversion cannot do. That arm predates
#98497 and is an
optimisation rather than the redundancy that was flagged.

For the tests, `turbo-unix-path` remains the place where the relative
path computation is covered,
untouched. What is left here is only what this layer adds on top: that
each method reaches for the
form it names, and that neither returns a path across filesystems.
Keeping the first of those is a
judgement call — a method delegating to the wrong free function is
precisely the bug #98497 existed
to fix, and it is invisible to the lower-level table — so it is asserted
with one descendant pair
rather than a table. Happy to drop it entirely if you would rather.

To check that the smaller test still earns its place,
`get_relative_request_to` was temporarily
pointed at the plain-path function; the test fails as it should.

<!-- NEXT_JS_LLM -->

<!-- fleet 3acc7e63-52a8-4472-89cd-b1bbb8ad59fa -->

---------

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-14 07:43:14 +00:00
Tobias Koppers c2e4f05fb1 Serialize turbo-tasks trybuild tests (#98415)
### What?

Run all `turbo-tasks-macros-tests` trybuild fixture groups through one
test process and one generated Cargo project lifecycle.

### Why?

Trybuild uses one generated Cargo project per package. Cargo-nextest
previously launched the five test groups as separate processes, allowing
them to overwrite that project's manifest while another group was
compiling. On canary this surfaced as diagnostics from the wrong
fixture, missing `trybuild00N` bin targets, and compile-fail cases
incorrectly succeeding.

The tracked stderr snapshots are correct; the instability is in
concurrent harness execution rather than compiler output or the SWC
version.

### How?

A single `TestCases` instance now queues every existing pass and
compile-fail glob. This preserves per-fixture diagnostics and coverage
while preventing cross-process manifest collisions. A comment documents
the shared-project invariant so the groups are not split into concurrent
tests again.

### Verification

- `cargo nextest run -p turbo-tasks-macros-tests --cargo-profile
release-with-assertions --no-fail-fast`
- `cargo fmt --all -- --check`
- `git diff --check`

<!-- NEXT_JS_LLM -->


<!-- fleet ae1d9ec9-156e-4b53-80c0-f24bf0b22f13 -->

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-14 09:11:02 +02:00
next-js-bot[bot] 748cc4a6e8 v16.4.0-canary.28 v16.4.0-canary.28 2026-09-12 23:21:08 +00:00
Tobias Koppers 10f370c1f3 test: stabilize OpenTelemetry collector lifecycle (#98427)
## What?

Stabilize the OpenTelemetry instrumentation e2e suite by keeping its
local span collector available for each application's full lifetime.
Reset collected span state between tests without closing the collector,
and cover continuity across a test boundary.

## Why?

The suite kept Next.js and custom-server exporters alive across tests
while closing and rebinding their collector port after every test.
Exports during that gap failed with closed/reset/refused socket errors
before trace assertions timed out or observed incomplete trees. Aligning
the collector lifetime with the exporter removes that race instead of
masking it with retries, longer timeouts, or weaker assertions.

## How?

- Start the collector before starting each managed application.
- Keep the listener bound until application teardown, while clearing
only buffered spans in `beforeEach`.
- Replace per-test collector reconnects across the affected suites and
remove the disabled-fetch suite's fixed teardown delay.
- Assert that one collector remains connected, resets its state, and
receives a fresh trace across sequential tests.

## Verification

- Webpack dev / React 18.3.1: 5 consecutive focused runs, 54 tests
passed per run
- Webpack start / React 18.3.1: 5 consecutive focused runs, 97 passed
and 13 existing skips per run
- `pnpm test-types`
- Prettier and ESLint on the changed files

<!-- NEXT_JS_LLM -->

<!-- fleet c7f6c6e4-56b2-4648-bc2d-88b2f256bb7a -->

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-12 20:52:31 +02:00
Will Binns-Smith d5276f04a1 Make TurbopackResult payloads disjoint (#98575)
### What?

Refactors the JavaScript-facing `TurbopackResult<T>` into a stable
wrapper whose payload lives under `value` and whose issues remain
top-level. Native API consumers, entrypoint conversion,
development-server paths, HMR handling, and direct API tests now follow
the disjoint shape.

Two event subscriptions that had inaccurate wrapper declarations now
expose their existing plain runtime payloads explicitly: update-info
events remain `UpdateMessage`, and compilation events remain
`CompilationEvent`. Nullable native entrypoint payloads are also
declared accurately and normalized at the JavaScript API boundary.

### Why?

The previous intersection-based representation merged payload fields
with result metadata. That allowed fields such as `issues` to overwrite
one another and caused non-object payloads to be discarded, making the
result shape depend on `T`. A dedicated payload property avoids those
collisions and preserves every payload type, including `null`.

Keeping plain event streams distinct from result wrappers also ensures
`TurbopackResult<T>` consistently means the native API actually provides
wrapper metadata.

### How?

The N-API serializer now always creates a fresh wrapper rather than
mutating an object payload. The shared TypeScript type models that
wrapper directly and requires an explicit payload type. Conversion
layers replace only the nested payload while explicitly preserving
wrapper issues. Call sites continue to process wrapper issues while
reading domain data through `value`.

The update-info and compilation-event declarations were aligned with
their native callback types instead of introducing new runtime wrappers
for streams that do not collect issues. Rust `Option<NapiEntrypoints>`
payloads are declared as nullable, then normalized to the existing
empty-entrypoints representation after a null-safe route check.

### Verification

- `pnpm build-all`
- `pnpm --filter=next types`
- `pnpm swc-build-native`
- `cargo check -p next-napi-bindings`
- `cargo fmt --all -- --check`
- ESLint and Prettier on changed files
- `pnpm test-dev-experimental-turbo
test/development/app-aspath/app-aspath.test.ts`
- `pnpm test-dev-turbo test/development/basic/next-rs-api.test.ts` — 26
passed, 1 skipped, 15 snapshots; Jest reported lingering open handles
after the green summary
- `pnpm test-dev-turbo
test/development/app-dir/concurrent-install/concurrent-install.test.ts`
- `pnpm test-dev-experimental-turbo
test/development/app-dir/concurrent-install/concurrent-install.test.ts`

<!-- NEXT_JS_LLM -->

<!-- fleet 74688340-f99a-462e-8f75-88d12c88000a -->

---------

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-09-11 16:25:09 -07:00
next-js-bot[bot] d4c9abe04d v16.4.0-canary.27 v16.4.0-canary.27 2026-09-11 23:21:05 +00:00
Niklas Mischkulnig f41441a0c8 Durable use cache: optimize env var existence checks (#98504)
We now separately track code that accesses non-inlined env vars at
runtime in two categories
- (existing) actual reads, the full value is accessed
- (new) only unset/falsy/truthy is read at runtime

For the second case, we only need to invalidate use-cache functions when
the given env var transitioned betwen unset/falsy/truthy. But
transitioning between two different truthy values doesn't caused
invalidation.
2026-09-11 22:19:32 +02:00
Jimmy Miller 30d6d8f497 Adds this.mode for webpack loaders (#98532)
Pretty straightforward. But a number of loaders depend on it including

- postcss-loader
- sass-loader
- stylus-loader
- vue-loader
- nunjucks-loader
- thread-loader
2026-09-11 12:05:03 -07:00
next-js-bot[bot] 83cb4fb70f Upgrade React from 6c0e1047-20260908 to 019019be-20260911 (#98576) 2026-09-11 17:58:49 +00:00
Steven deed868499 feat(next/image): Add experimental imgOptMozjpeg config (#98571)
In a previous PR, `mozjpeg` was set to true which might be the reason
output jpeg spikes cpu.

- https://github.com/vercel/next.js/pull/65846

I made a benchmark and found that disabling MozJPEG saved 72% CPU, with
average outputs 31% larger.

- https://github.com/lovell/sharp/issues/4603

So this PR adds a new experimental flag to enable (or rather disable)
mozjpeg and try with real workloads. This is useful becaues in many
cases, cpu is more costly than bandwidth since most CDNs provide [near
unlimited bandwidth](https://vercel.com/blog/introducing-flat-rate-cdn).
2026-09-11 17:58:21 +00:00
Jimmy Miller d155ba9ebf [turbopack] Lazily compile dynamic imports in development (client side) (#97203)
## Summary

Defer compiling client-side dynamic import targets in Turbopack
development until the browser requests their manifest chunk. This avoids
compiling untouched dynamic imports while preserving server-side
imports, CSS loading, Server Actions, source maps, and Fast Refresh
behavior.


One caveat on this is next/dynamic still does some eagerness. I looked a
bit at changing things. But it breaks some of the guarantees there and
decided to leave it off the table.

---------

Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
2026-09-11 07:29:43 -07:00
Joseph c5741d5230 fix(devtools): handle pointercancel when dragging the indicator (#98506)
Fixes #98468

## Claude explanation of the fix

`useDrag` listened for `pointermove` and `pointerup`, but never
`pointercancel`.

When a user agent cancels a gesture — a browser or system gesture takes
over, a second finger arrives — it fires `pointercancel` and no
`pointerup`, implicitly releasing pointer capture as it does. So
`cancel()` never ran: the state machine stayed `{ state: 'drag' }` and
`cleanup.current` was never invoked, leaving the
`pointermove`/`pointerup` listeners on `window`.

The next `pointerup` anywhere on the page then hit those orphaned
listeners, reached `cancel()` with the state still `'drag'`, and called
`releasePointerCapture()` on a pointer that no longer existed — the
reported `NotFoundError`. Each cancelled drag also leaked another
listener pair.

This registers `pointercancel` alongside `pointerup` and removes it in
the same cleanup, so the machine unwinds when a gesture is cancelled;
and it releases pointer capture only when `hasPointerCapture()` says it
is still held, which is preferable to `try/catch` swallowing genuine
faults too.

Note `touch-action: none` (#97723) removed the common touch trigger, but
not the defect: on canary a forced `touchCancel` still throws.


https://github.com/user-attachments/assets/45a9524d-9ffa-40b6-b59a-5af9d947f8c7

## Fixed version


https://github.com/user-attachments/assets/b5ada68c-7442-42ef-a256-bacc512fbeca

## Browser checks

- [x] Chrome, as describe by the bug report
- [x] FF works fine pre and post fix (with and without the pointer
simulation)
- [x] iOS safari simulator, before the fix, I can't see an error, but,
the drag gets frozen, with this fix it works correctly


https://github.com/user-attachments/assets/bcfdc786-bada-4462-b530-299523fdca6d

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 16:01:14 +02:00
Sebastian "Sebbie" Silbermann 2d417dae1d [ci] Show full logs under GitHub Actions debug logging (#98501)
Logs of passed tests are hidden by default in CI.
There's an argument to be made to always show them in CI. For now we
just wire up GitHub's built-in "re-run with debug logging enabled" to
show logs of passed tests.

<img width="1358" height="936" alt="CleanShot 2026-09-10 at 19 00 18@2x"
src="https://github.com/user-attachments/assets/70fb9c56-c681-4394-958b-7fa9aea19970"
/>

Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>
2026-09-11 13:46:40 +02:00
James Argarin f464e32ec0 fix(typegen): merge cacheLife types into next/cache instead of shadowing it (#98252) 2026-09-11 13:44:23 +02:00
Sebastian "Sebbie" Silbermann 64596d1f53 [test] Fix proxy-request-with-middleware deploy test (#98553)
The fixture's API route proxies the incoming request to
`http://<host>/api/post`. On Vercel, that plain-HTTP hop hits the edge's
http→https upgrade and the handler pipes the resulting 308 back to the
test client, which then has to replay the POST body across the redirect.
Node.js' fetch implementation cannot replay bodies across 307/308
redirects and fails the request with
`UND_ERR_REQ_CONTENT_LENGTH_MISMATCH`, which broke this test in deploy
mode after the test harness switched from `node-fetch` to global `fetch`
in #98195.

This change proxies over `https` when `VERCEL_URL` is set so no
client-visible redirect occurs, while local dev and start modes are
unaffected since they keep using plain HTTP.

Fixes

```
TypeError: fetch failed

      29 |         'x-custom-header': 'some value',
      30 |       }
    > 31 |       const res = await fetchViaHTTP(next.url, `api`, '', {
         |                   ^
      32 |         method: method.toUpperCase(),
      33 |         headers,
      34 |         body: method.toLowerCase() !== 'get' ? body : undefined,

      at Object.<anonymous> (e2e/proxy-request-with-middleware/test/index.test.ts:31:19)

    Cause:
    RequestContentLengthMismatchError: Request body length does not match content-length header
```
--
https://github.com/vercel/next.js/actions/runs/34543344283/job/103105861941#step:35:555
2026-09-11 13:43:34 +02:00
Sebastian "Sebbie" Silbermann 033bc32c02 [ci] Remove the broken Test examples workflow and its test suite (#98517)
Removes the `Test examples` workflow and its orphaned test suite. The
workflow fails every scheduled run: its pinned Playwright docker image
(v1.35.1) is incompatible with the Playwright version the repo now
requires (v1.61.0), so the browser cannot launch. It also no longer runs
what it was created for: since the `run-tests.js` glob root moved from
`test/` to the repository root, the `--type examples` string-prefix
filter `examples/` matches test files inside example apps instead of
`test/examples/examples.test.ts`, leaving that suite without any runner.

This deletes `.github/workflows/test_examples.yml`, the `test/examples/`
suite, and the `--type examples` filter in `run-tests.js`. The
`examples/` prefix stays in the default-run exclusion list so test files
inside example apps (which run with each example's own jest/vitest
setup) are still not picked up by bare `run-tests.js` invocations.

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-11 10:53:28 +02:00
Hendrik Liebau 1a295a631c Align fallback parameter staging with shell validation (#98512)
`next dev` reported missing Suspense boundaries in layouts that the
build accepted. For `/[top]/items/[bottom]`, `generateStaticParams`
returned `[{ top: 't1' }]`, and the page wrapped its `bottom` access in
Suspense. A request for `/t2/items/b2` still reported the layout's
access to `top` as an error. Development treated both parameters as
unresolved because the requested `top` value was not generated, although
the required static shell only needed to defer `bottom`.

Production Cached Navigations used the same overly broad parameter set
and omitted eligible static content from repeat visits. Resumes also
reconstructed that set from the original build manifest, even after an
on-demand prerender had produced a more complete shell.

This replaces the alternative proposed in #98460. That proposal fixes
the development error by selecting a separate fallback parameter set for
validation while keeping response staging unchanged. The two sets are
not intended to differ for the same shell target. Correcting only
validation would preserve the incorrect staging decision and leave
production Cached Navigations without the eligible static content.

Staging and static-shell validation now use one `stagedFallbackParams`
set for each selected shell target. Required partial shells retain their
unresolved parameters, even when a later request can complete them.
Prerenders record their parameter set in postponed state, and resumes
use that recorded set rather than reconstructing it from the generic
source.

Dynamic RSC requests now read and revalidate the completed-shell cache
key, so they find partial artifacts that the fully resolved pathname
lookup missed. Request metadata and `RequestStore` both expose the set
as `stagedFallbackParams`. Action-only fallback detection checks actual
unresolved parameters instead of treating deferred values as missing.
2026-09-11 10:46:54 +02:00
Hendrik Liebau 79daac38ae Prevent synchronous IO from entering Cached Navigations (#98511)
Cached Navigations incorrectly reused uncached timestamps as static page
content. A repeat visit showed the previous request's `Date.now()`
result while the new response was still pending. This affected both
dynamic RSC responses and the RSC payload embedded in the initial HTML.

The production renderer left synchronous IO untracked because it assumed
the build had already checked it. A private cache exposed the gap: the
static prerender suspended at the cache call and never reached the
`Date.now()` after it. A production request resolved the private cache
during the static stage and included the uncached timestamp in the
reusable response.

Production staged renders now end the static stage when synchronous
request-time IO occurs. They mark the interrupted static and shell
prefixes as empty, while the rest of the response renders normally. The
client skips those prefixes instead of waiting for a Flight root that
the interrupted stage may not have emitted.

The regression tests cover dynamic RSC navigation, initial HTML
hydration, and full prefetching with a paramless private-cache fixture.
2026-09-11 10:46:53 +02:00
Sebastian "Sebbie" Silbermann 4e906f163c [ci] Fix scheduled Rspack test job (#98508)
This workflow is responsible for periodically updating the manifest. The
workflow broke in https://github.com/vercel/next.js/pull/97590 since the
[called workflow requires `pull_request:
write`](https://github.com/vercel/next.js/blob/626ab223bae3f9b29b3ec8cbd18a58d86b871ec1/.github/workflows/integration_tests_reusable.yml#L172)
to open the update PR.

Fixes
```
[Invalid workflow file: .github/workflows/rspack-nextjs-build-integration-tests.yml#L10](https://github.com/vercel/next.js/actions/runs/34439645923/workflow)
The workflow is not valid. .github/workflows/rspack-nextjs-build-integration-tests.yml (Line: 10, Col: 3): Error calling workflow 'vercel/next.js/.github/workflows/integration_tests_reusable.yml@334052ffbb383395b74cac6a90e0b15c7317957b'. The nested job 'collect_nextjs_development_integration_stat' is requesting 'pull-requests: write', but is only allowed 'pull-requests: none'.
```
-- https://github.com/vercel/next.js/actions/runs/34439645923
2026-09-11 10:40:31 +02:00
Sebastian "Sebbie" Silbermann 87edae5980 [ci] Remove Turbopack test manifest pipeline (#98507)
The manifest is no longer considered for skipping test since
https://github.com/vercel/next.js/pull/81170. It also didn't update
anymore automatically so I removed updating areweturboyet.com which was
broken for a while now anyway when we switched away from static tokens.
2026-09-11 10:40:30 +02:00
next-js-bot[bot] ccef3db535 v16.4.0-canary.26 v16.4.0-canary.26 2026-09-10 23:19:52 +00:00
Jimmy Miller 637b8cfa5e Add in warning for workers around the fsevents bug (#98447)
We removed this because we fixed the original crashes. This crash does
not happen out of the box. But it isn't hard to trigger with plugins.
And we'd rather not have people experience this without being explicit.
2026-09-10 13:48:29 -07:00
Aurora Scharff 393de1ebe0 skill: add Partial Prefetching optimizer (#96471)
## Summary

- add a Partial Prefetching optimizer Skill for selected source Links
and destinations
- keep prefetch stages and Link policy in the public [Optimizing
prefetching
guide](https://nextjs.org/docs/app/guides/optimizing-prefetching), and
rely on the same static-shell documentation as the Cache Components
optimizer for cache placement, Suspense, loading UI, and authorization
- keep the Skill focused on the product decision, production `instant()`
loop, differential verification, and mutation freshness when an
optimization adds a cache around mutable data
- reference the canonical
[`unstable_prefetch()`](https://nextjs.org/docs/app/api-reference/functions/prefetch)
and
[`unstable_navigation()`](https://nextjs.org/docs/app/api-reference/functions/navigation)
docs instead of explaining the APIs again in the Skill
- link cache-writing optimizations to the canonical
[Revalidating](https://nextjs.org/docs/app/getting-started/caching-and-revalidating#revalidating)
documentation and verify a populated-cache mutation in the Skill and
eval
- bundle a self-contained production rig aligned with the related
adoption and optimizer Skills, tuned to record each source-link and
prefetch-stage contract
- add an eval that compares baseline, the bundled guide, and the local
Skill against the same prefetched-UI and cache-freshness goal
- document the optimizer in the AI agents guide

This follows the same guide/Skill split as #97737. Partial Prefetching
adoption preserves existing behavior; this optimizer is the later
decision about what is worth preparing before a click and what should
wait for navigation.

Related: #97712, #97737, #97759, #98122, #98175

## Eval

One run per treatment with Claude Opus 4.8 and Claude Sonnet 4.5, using
a fixed Claude Haiku 4.5 judge. The bundled-guide and Skill treatments
use a local Next.js tarball containing the new guide and API references.

| Model | Treatment | Criteria | Score change | Overall | Duration |
Tokens processed | Total run cost | Cost change |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Opus 4.8 | Baseline | 10/10 (100%) | — | Pass | 18m 42s | 3.39M |
$3.46 | — |
| Opus 4.8 | Bundled guide | 9/10 (90%) | -10 pp | Fail | 13m 37s |
3.02M | $2.84 | -18.0% |
| Opus 4.8 | Local Skill | 10/10 (100%) | +10 pp | Pass | 17m 22s |
2.80M | $2.54 | -10.6% |
| Sonnet 4.5 | Baseline | 2/10 (20%) | — | Fail | 15m 55s | 1.97M |
$1.06 | — |
| Sonnet 4.5 | Bundled guide | 5/10 (50%) | +30 pp | Fail | 15m 49s |
2.17M | $1.11 | +4.5% |
| Sonnet 4.5 | Local Skill | 8/10 (80%) | +30 pp | Fail | 15m 31s |
1.41M | $0.77 | -30.4% |

Token totals count each unique model message once and include uncached
input, cache creation, cache reads, and output. Cache reads account for
most of the total in every treatment. Total run cost applies the current
AI Gateway base rates to each token category: Opus 4.8 costs $5/M input,
$6.25/M cache writes, $0.50/M cache reads, and $25/M output; Sonnet 4.5
costs $3/M input, $3.75/M cache writes, $0.30/M cache reads, and $15/M
output. The six agent runs cost $11.78 in total.

Compared with baseline, the Opus Skill run maintained a 100% criteria
pass rate while reducing cost by 26.7%. The Sonnet Skill run improved
the criteria pass rate from 20% to 80%, a 60 percentage-point increase,
while reducing cost by 27.3%.

The stronger model could solve the fixture without the Skill, although
it independently searched the bundled Next.js documentation and took
slightly longer than the Skill treatment. The Opus guide-only run
implemented the correct behavior but missed the required
RED-before-GREEN proof.

The Sonnet treatments show a clearer progression. Baseline did not
implement the required caching, navigation stage, `instant()` contract,
or cache invalidation. The bundled guide added the cached summary and
production test, but missed the navigation stage, deferred-state
assertions, tag invalidation, and RED-before-GREEN proof. The Skill
treatment completed the `instant()` RED-to-GREEN loop and final
production behavior, but left `connection()` in the related-sessions
read and used path revalidation instead of tagging the cached read. It
therefore passed 8 of 10 criteria but failed the overall eval.

All six measured runs completed through grading. Chromium and its system
dependencies are provisioned before the agent starts, so missing browser
libraries did not determine these results.

## Verification

- `pnpm --filter=next build`
- `pnpm prettier --check` for the changed guide, prompt, Skill, and eval
fixture
- `pnpm exec tsc -p
evals/evals/agent-051-optimize-partial-prefetching/tsconfig.json
--noEmit`
- `node --check run-evals.js`
- `git diff --check`
- packed a local Next.js tarball and confirmed it contains the optimizer
guide plus the `navigation()` and `prefetch()` references
- verified the `navigation()` contract in the minimal reproduction and
NextBeats on Next.js 16.4.0-canary.22
- merged the latest `canary`, preserving both generic fixture setup and
Playwright provisioning

<!-- NEXT_JS_LLM -->
2026-09-10 22:27:36 +02:00
Will Binns-Smith 6fbba169cd bundle-analyzer: add table view to single-build analyzer (#93523)
Extends the view toggle (introduced in the previous PR for compare mode)
to single-build mode as well. The table view lists all sources for the
selected route with sizes and env badges. Internally it reuses
`DiffTable` in `mode="single"` by diffing the build against itself,
which produces an all-`identical` summary — this keeps a single
source-listing implementation regardless of mode.

<!-- NEXT_JS_LLM_PR -->

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-09-10 12:47:56 -07:00
Will Binns-Smith 0bdea6782d bundle-analyzer: add table view to compare mode (#93522)
Adds a table view to compare mode, switchable from the treemap via a
toggle in the top bar. The `DiffTable` lists every source file with its
A/B sizes, delta, and env badges, and supports text search. Removed
sources (which can't appear in the treemap since they don't exist in the
current build) are only visible here.

The view toggle defaults to Table when entering compare mode and resets
to Treemap when leaving it.

<!-- NEXT_JS_LLM_PR -->

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-09-10 12:47:56 -07:00
Will Binns-Smith 2f3f74b13f bundle-analyzer: add compare view with treemap diff (#93521)
Adds a compare mode to the bundle analyzer. When the user picks a
historical snapshot from the new baseline picker in the top bar, the UI
switches into compare mode: routes are diff'd by name and size, and the
per-route view shows a `DiffTreemap` that colors each source tile by its
change status (added/removed/grew/shrank/unchanged).

The compare sidebar shows the import chain for the selected tile on
either the baseline ("A") or current ("B") side. Route totals are
fetched in parallel so the route picker can sort by largest impact and
classify routes as `changed`/`identical` rather than just name-presence.

<!-- NEXT_JS_LLM_PR -->

---------

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-09-10 12:47:55 -07:00
Sebastian "Sebbie" Silbermann da6e8b65fa [testmode] Stop using deprecated url.parse (#98194)
`unstable_getResponseFromNextConfig` parsed the request URL with the
deprecated `url.parse()` (DEP0169)

This change constructs the same pathname/query/protocol/host shape from
the WHATWG `URL` API instead, keeping `protocol` and `host` null for
relative URLs so the existing rewrite base fallback keeps working. Query
parsing goes through the existing `searchParamsToUrlQuery` helper, which
decodes `+` as a space where the legacy parser left it untouched; we
accept that edge-case difference in this testing utility.
2026-09-10 16:42:56 +00:00
Kazma Endo dcbfff7789 fix(next/image): don't share the requester's socket with the internal image response (#98168)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 18:40:17 +02:00
Hendrik Liebau ac55fd9965 Fix duplicate background revalidation for 'use cache' (#98446)
An early `cacheHandler.set()` call previously made subsequent same-key
`get()` calls wait for generation and persistence. This prevented
another background revalidation, but those requests waited instead of
receiving the existing stale data.

The work in #94694 added private-cache persistence in dev. Its helper
refactor made cross-request invocations wait for cache writes before
retrying root-param-specific lookups. It also moved the coarse-key
`set()` call after entry collection. That unintentionally allowed
subsequent requests to receive stale data and start another background
revalidation while one was already running.

This change tracks background work separately for each lookup key within
a runtime instance. Subsequent requests can receive the existing stale
entry without waiting for generation or starting duplicate work. The
task remains registered until collection and its cache writes settle.
Each lookup records existing work before calling `get()`, so a slow
lookup does not start another revalidation after the task it observed
has completed.

The handler still receives `set()` after collection, including
asynchronous rendering of the cached result. Subsequent `get()` calls
can still block while the handler persists the entry. We accept that
remaining wait to preserve the cache-handler contract without bypassing
handler reads.

**Alternatives Considered**

- Restore early `set()` calls and rely on the handler's pending-set
promises. This would prevent duplicate regeneration, but would also make
subsequent requests wait for generation again instead of receiving stale
data.

- Extend the existing cross-request sharing mechanism through background
revalidation. We prototyped retaining stale and regenerated results, but
that required additional stream ownership, invalidation checks, and
root-param matching. The separate background-task map avoids those
changes and leaves cold and foreground generation unchanged.

Fixes #98326
2026-09-10 18:33:53 +02:00
Benjamin Woodruff ‮ c1156571cd Turbopack: Deduplicate project options documentation (#98465)
By documenting both the partial and non-partial versions of these
objects, we were documenting everything multiple times.

Just reference the non-partial object from the partial object's
documentation.
2026-09-10 08:59:49 -07:00
Josh Story e674165f36 Report incompatible parallel route slots (#97430)
With strict route matching a layout can have sibling slots whose pages
can never form a complete route. For example:

```
app/
  layout.tsx
  @left/
    foo/
      page.tsx
  @right/
    bar/
      page.tsx
```

`/foo` is always missing `@right`, and `/bar` is always missing `@left`.
The pages were authored as route targets, but neither URL can construct
the complete slot tree owned by `app/layout.tsx`. Catchall pruning
removes broad matchers that cannot render and the unmatched-page
validation reports dangling files, but neither explains this
project-level mistake.

This validates the finalized ordinary matcher set and reports each
incomplete URL at the layout that owns the conflicting slots. Adding the
corresponding pages or an explicit `default.tsx` makes the topology
valid. Framework-owned defaults do not invent a `children` slot, and
interception matchers remain exempt because they intentionally describe
partial router state updates.

<!-- NEXT_JS_LLM -->
2026-09-10 08:02:34 -07:00
Josh Story dea15ec603 Require canonical routes for interception routes (#97428)
Interception routes only describe the branch used during a soft
navigation, but every URL they can produce still needs to be handled by
an ordinary App Router page matcher for a direct request or refresh.
Today an interception-only URL can appear to work during client
navigation and then 404 when the same URL is loaded directly. This makes
strict route matching reject that configuration.

For example, this setup is incomplete:

```
app/
  @modal/
    (.)photo/
      [...id]/
        page.tsx
  photo/
    [id]/
      page.tsx
```

The ordinary route handles `/photo/1`, but the interception route also
handles `/photo/1/2`, which has no ordinary page matcher. Widening the
ordinary route makes the setup valid:

```
app/
  @modal/
    (.)photo/
      [...id]/
        page.tsx
  photo/
    [...id]/
      page.tsx
```

The validation checks coverage rather than requiring an identical
matcher. A broader catchall can cover the intercepted pattern, and
multiple ordinary matchers can cover different accepted lengths. For
example, `items/page.tsx` together with `items/[...rest]/page.tsx`
covers an intercepted `items/[[...parts]]` matcher. Route handlers and
generated metadata routes do not count as canonical page matchers.

Webpack and Turbopack now apply the same coverage check and report the
interception matcher and the canonical pattern that is not fully
covered. Existing interception fixtures now include semantically
complete ordinary routes instead of relying on interception-only URLs.

<!-- NEXT_JS_LLM -->
2026-09-10 08:02:34 -07:00
Josh Story 7b7cee3086 Error when app pages do not match any route (#97401)
## What?

This reports an error under `experimental.strictRouteMatching` when a
`page.tsx` is left out of every retained matcher. A page can lose its
own broad catchall matcher and still be valid if it participates in a
more specific matcher, so this checks the final matcher set instead of
treating every pruned candidate as an error.

Both Webpack and Turbopack report every unmatched page in development
and production builds.

## Why?

Pages that are never selected by any complete loader tree are
unreachable by construction. This usually means parallel slots define
incompatible paths without matching pages or defaults, and silently
ignoring those files makes the project look like it has routes that
cannot actually exist.

For example:

```text
app/disagreeing-slots/
├── layout.tsx
├── @first/foo/page.tsx
├── @second/bar/page.tsx
└── [...slug]/page.tsx
```

Every candidate is incomplete: `/foo` has no match in `@second`, `/bar`
has no match in `@first`, and URLs handled only by the catchall have no
matching page or applicable default in either named slot. Strict route
matching prunes all three matchers, and this change reports all three
page files instead of silently leaving them out of the build.

The same validation catches a real `children` branch that only becomes
incomplete below its page:

```text
app/declared-children/
├── layout.tsx
├── page.tsx
└── @panel/
    ├── default.tsx
    └── details/page.tsx
```

`/declared-children` is valid, but `/declared-children/details` only has
a page in `@panel`. Because the declared `children` slot has no matching
page or default at `/details`, that matcher is pruned and
`@panel/details/page.tsx` is reported as unreachable. The root
`page.tsx` is still part of the valid `/declared-children` matcher and
is not reported.

A pruned matcher is not automatically an error. If
`@slot/[...parts]/page.tsx` is combined into retained `/foo` and `/bar`
matchers, that page still participates in routes and remains valid.

## How?

Webpack snapshots the authored app page paths before pruning and
compares them with the pages used by the retained app paths. Turbopack
collects authored page source files from the directory tree and compares
them with the files referenced by the final accepted loader trees. The
tests cover disagreeing named slots, declared `children`, optional
catchalls, route groups, nested slots, and the valid case where a pruned
catchall is still used by more specific matchers.

## Verification

- `pnpm test-start-turbo
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-start-webpack
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-dev-turbo
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-dev-webpack
test/e2e/app-dir/unmatched-app-pages/unmatched-app-pages.test.ts`
- `pnpm test-start-turbo
test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts`
- `pnpm test-start-webpack
test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts`

<!-- NEXT_JS_LLM -->
2026-09-10 08:02:33 -07:00
Tobias Koppers ec62d6a48f fix(turbopack): add get_relative_request_to for paths used as module requests (#98497)
### What?

Adds `get_relative_request_to`, next to `get_relative_path_to`, for
callers that need a relative
path they can hand to the resolver as a module request. It returns
`./c`, `../c` or `.`, where
`get_relative_path_to` returns `c`, `../c` or `.`.

Fixes `import.meta.glob`'s `base`-relative keys, which currently come
back without the leading `./`:
`{ base: '../outside' }` produces `one.js` where Vite (and our own docs,
and the existing execution
fixture) expect `./one.js`. That fixture is failing on `canary` today;
it passes here unchanged.

### Why?

The `./` prefix is not decoration — it is what makes a string a
*relative* request. Without it, `c`
resolves as the package `c` rather than the file next to `from`.

`get_relative_path_to` stopped emitting the prefix for a path below
`from`, and every caller that
builds a request had to add it back by hand. Eight of them do, each with
the same three lines. That
works, but it puts the requirement in the call sites instead of the API,
and nothing tells a new
caller that it exists — so the ninth request-building call site,
`import.meta.glob`'s `base`-relative
key, was written without it and silently produced the wrong keys. The
two changes were authored
independently and each was green on its own; only together do they
produce the bug, which is why CI
caught it on `canary` rather than on either pull request.

Rather than add a tenth hand-written prefix, this makes "give me a
request" something you can ask
for. The failure mode it removes is the silent one: a caller that
forgets is no longer possible,
because the choice is now the function you call.

### How?

`get_relative_request_to` shares its traversal with
`get_relative_path_to` through one private
helper, so the two can't drift; the only difference is whether a result
that would be bare gets the
prefix. `get_relative_path_to` keeps its signature and behaviour
exactly, including the fast path
that returns `target` by reference when `from` is empty — the request
form can't borrow there, and
the unit tests assert that difference so the fast path isn't reused by
mistake. `FileSystemPath`
gains the matching method.

The eight hand-written prefixes now call the new function and drop their
snippets. Deciding which
call sites to convert needed care, because the callers of
`get_relative_path_to` genuinely want
different things, and the ones that want a plain path deliberately keep
it:

- source-map paths reported back to the client,
- the Next.js template expander, whose invariant check expects the bare
form,
- the paths written into NFT files, which are data rather than requests.

Two locals that hold requests rather than paths are renamed accordingly,
and one leftover
`strip_prefix("./")` is removed — it had become a no-op undoing a prefix
that is no longer produced.

### Testing

The `import-meta-glob-relative` execution fixture already asserts the
correct keys and currently
fails, so the fix is verified by that fixture passing **unmodified** —
its expectations were right
all along.

The unit test that covered these cases before the prefix went away is
restored, now run against both
functions and against `FileSystemPath`, which had been left with no
coverage of either method. Its
original assertions turn out to describe the request form exactly, so
they pin that
`get_relative_request_to` reproduces the earlier contract rather than
merely resembling it.

Beyond that: the full `turbopack-tests` suite, and the `next-server-nft`
and `next-taskless` tests,
which are what would break if a call site that wants a plain path had
been converted by mistake.

<!-- NEXT_JS_LLM -->

<!-- fleet 3acc7e63-52a8-4472-89cd-b1bbb8ad59fa -->

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>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
2026-09-10 13:06:38 +00:00
Sebastian "Sebbie" Silbermann 1329212686 [ci] Cleanup orphaned next-stats-action (#98348)
Became orphaned in https://github.com/vercel/next.js/pull/97792. Another
script and the test harness needed some utils from `next-stats-action`
so I moved that into `test/lib/link-packed-packages`.
2026-09-10 13:50:36 +02:00
Sebastian "Sebbie" Silbermann 76a0bdbdf4 [scripts] Move scripts (and benchmarks) off of node-fetch (#98347)
We're using Node.js versions with a built-in `fetch` implementation in
all of these scripts.
2026-09-10 13:50:35 +02:00
Sebastian "Sebbie" Silbermann e8955cce0f [test] Move fixtures off node-fetch (#98346)
I don't believe they tested `node-fetch` specifically and rather used it
because Node.js didn't have a built-in `fetch` implementation.
2026-09-10 13:50:35 +02:00
Sebastian "Sebbie" Silbermann 6ba71046b0 [test] Move the harness off node-fetch (#98195)
Node.js ships with a built-in `fetch` now so `node-fetch` is no longer
necessary. Mostly motivated by tracing Node.js deprecation warnings
which originated from `node-fetch` by calling the deprecated
`url.parse`.

Call sites keep working through a compatibility type on `fetchViaHTTP`
that translates node-fetch-only options: Instead of `agent` we pass to
`http(s)` directly, `timeout` becomes `AbortSignal.timeout`, and Node.js
readable streams are accepted as bodies with `duplex: 'half'` set
automatically.

The `abort-controller` polyfill is dropped since its signal type
predates the current AbortSignal and undici would not honor it.
`node-fetch` stays installed because `scripts/generate-release-log.mjs`,
`scripts/reset-project.mjs`, and `scripts/update-google-fonts.js` still
import it (follow-up material). Fixture apps will be migrated
separately.
2026-09-10 13:50:35 +02:00
Tobias Koppers ed1aab5d38 fix(turbopack): resolve ../ and /-rooted import.meta.glob patterns (#96557)
### What?

`import.meta.glob()` now resolves `../`-relative and
`/`-project-root-absolute
patterns (and `base`), validates every pattern (positive and negative)
for
whether it escapes the filesystem root, and shares the underlying
"resolve leading `./`/`../` against a directory" primitive with
`next-core::relativize_glob` (used by `outputFileTracingIncludes`)
instead of
duplicating it.

### Why?

`import.meta.glob()` handed its patterns straight to Turbopack's `Glob`,
which
matches relative to the scanned directory and understands neither a
leading
`/` nor `..` segments. `import.meta.glob('../../content/*.js')` and
`import.meta.glob('/content/*.js')` therefore silently returned `{}`,
and a
`base` that walked above the filesystem root failed with an internal
error
and a panic report instead of a normal diagnostic. A negative
(exclusion)
pattern that escaped the root was silently dropped, which is the worst
outcome for an exclusion — it would silently include files the user
asked to
exclude.

### How?

- Patterns are split into the directory they're rooted in and the
remaining
glob, matched relative to a single common scan directory covering every
  positive and negative pattern. `/` is resolved from the project root,
matching Vite's rule that patterns are relative to the importing file or
  absolute from the project root.
- Map keys follow the pattern too: `base` now produces base-relative
keys
  (`'./zeta.js'` instead of `'../../content/zeta.js'`, matching Vite), a
  project-root-absolute pattern produces project-root-absolute keys, and
  origin-relative keys are unchanged otherwise.
- Every pattern is validated up front; one that leaves the project (a
`base`,
a positive pattern, or a negative one) reports an error issue at the
call
  site and yields an empty object, instead of crashing or being silently
  dropped.
- The `./`/`../`-resolution loop is extracted into
  `turbo_tasks_fs::glob::relativize_glob`, a crate both `next-core` and
`turbopack-ecmascript` already depend on. `next-core::relativize_glob`
is
now a thin wrapper over it (same name, signature and error message); its
5
unit tests are removed since the shared primitive's own tests cover the
  same cases.

<!-- NEXT_JS_LLM -->

Co-authored-by: Niklas Mischkulnig
<4586894+mischnic@users.noreply.github.com>

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-10 08:42:37 +02:00
next-js-bot[bot] 334052ffbb v16.4.0-canary.25 v16.4.0-canary.25 2026-09-09 23:21:11 +00:00
Josh Story 1f60b5f193 test: restore deployment exclusion for shared Cache Components tests (#98464)
## Summary

Restore `skipDeployment` and the early return in the shared Cache
Components error-test runner. The migration in #98154 replaced them with
a force-gate pragma, but the gate transformer only processes `.test.*`
files, so the pragma in `shared.util.ts` did not preserve the deployment
exclusion.

This is a temporary rollback to unblock deploy testing. Moving the test
definitions out of the helper and migrating them again will be evaluated
separately; this PR does not change the transformer or refactor the
tests.

## Verification

- Ran all 12 importing test files through Jest in deploy mode with
deployment setup mocked to throw before any fixture or deployment work.
Before the rollback, all 12 reached that guard and failed. After the
rollback, all 12 take the legacy exclusion path and no deployment setup
is attempted.
- `pnpm typescript --skipLibCheck` passes.
- Full type-check reports the existing nine Octokit dependency
declaration errors; no errors in the changed file.

<!-- NEXT_JS_LLM -->
2026-09-09 23:15:45 +00:00
Josh Story cd7fa80089 Resolve metadata independently across parallel routes (#97345)
## Motivation

Parallel routes are separate rendering branches, but the existing
metadata resolver collects every matched slot into one shared chain. As
a result, traversal order can decide which slot supplies the document
head, and metadata errors from one slot can interfere with another.

## What this does

This adds a fork-aware resolver behind
`experimental.parallelRouteMetadata` while retaining the existing
resolver as the default. Each slot resolves metadata and viewport
independently, with isolated accumulators, and a single branch is
selected for the document head at every fork.

The built-in selector prefers real routes over implicit fallbacks and
prefers `children` when it contributes head data. Otherwise it chooses
the branch with the deepest head definition, using route depth and
lexical slot order only as stable tie-breakers.

Head selection is separate from rendering outcomes. Every slot gets its
own `MetadataOutlet`, which replays navigation and regular errors for
that slot regardless of which branch supplied the head.

## Review notes

- `generateMetadata`, `generateViewport`, and generated static metadata
still start eagerly; a fork does not serialize sibling branches or
parent/child generation.
- Branch state is isolated only where the tree forks, preventing
metadata mutation from crossing between slots.
- An outlet rejects as soon as either metadata or viewport rejects, but
resolves only after both succeed. This lets one failing result reach the
slot boundary even if the other never settles.
- Existing navigation convention metadata is preserved for the selected
branch. Errors from unselected branches remain observable only through
their own rendered outlets.
- The old resolver remains available behind an explicit flag so this
implementation can be compared or disabled during rollout.

## Verification

- `pnpm --filter=next types`
- Metadata resolver unit coverage
- Parallel-route metadata, navigation, and not-found coverage in
development and production with Turbopack and webpack

<!-- NEXT_JS_LLM -->
2026-09-09 16:13:17 -07:00
Aurora Scharff 2aaa6e810a docs: preserve route behavior during Cache Components adoption (#98275)
## Summary

- Explain that removing `dynamic = 'force-dynamic'` moves a route from
full request-time rendering to Cache Components' Partial Prerendering
model, then document the choices for its request-time work.
- Preserve routes that previously used `dynamic = 'force-static'` or
`dynamic = 'error'` instead of leaving them under `instant = false`,
which can move rendering to request time and reduce prefetched UI.
- Clarify that existing `fetch` and `unstable_cache` caching can remain
in place during adoption.
- Combine the incremental checkpoint and completion coverage into one
eval that verifies both stages of the migration.
- Make the adoption skill honor an explicit incremental strategy before
continuing through a complete migration.
- Remove the unrelated `.env*` ignore and restore the existing agent-054
eval registration.

## Eval results

`agent-047-adopt-cache-components` runs the complete incremental
migration with `openai/gpt-5.6-luna` through the Codex harness. It
retains the original five checks for the first shippable checkpoint and
the original seven checks for completing the migration.

| Treatment | Result | Notes |
| --- | --- | --- |
| Baseline | 11/12 | Preserved the final behavior, but treated the work
as a fresh migration instead of explicitly continuing from the recorded
first checkpoint. |
| Guide (`AGENTS.md`) | 8/12 | Built successfully, but rewrote the
existing `unstable_cache`, changed the catalog's hourly cadence, moved
its timestamp to request time, and skipped the recorded incremental
checkpoint. |
| Adoption skill | 12/12 | Preserved both cache lifetimes and static
contracts, recorded a passing incremental checkpoint, removed the
remaining opt-outs, and completed the migration. |

## Verification

- `pnpm build`
- Luna eval runs for the baseline, guide, and adoption-skill treatments
- Prettier and ESLint checks for the changed guide, skill, and eval
files
- `git diff --check`

<!-- NEXT_JS_LLM -->
2026-09-09 23:04:34 +00:00
Benjamin Woodruff ‮ be2c3fdb3a Add a fast-path to 'get_relative_path_to' for a potential common case when 'from' is empty (#98440)
When creating an NFT file in
https://github.com/vercel/next.js/pull/98003, I store a generic list of
"base" paths and then construct the final path in the JSON file as
relative to the base path. For most files, these paths are relative to
the `*.nft.json` file, but for additional roots, this base path is
always an empty string (relative to the root of the filesystem), so
being able to re-use the original `RcStr` instead of constructing a
brand new string may be beneficial.

This also moves the unit tests to be colocated next to the functions
that are actually being tested.
2026-09-09 15:54:55 -07:00
Aurora Scharff 526c492a60 docs: document React browser API (#97621)
### What?

- Documents React Canary's new `browser` API in the SPA guide
- Adds examples for marking a Client Component as browser-only and
wrapping it in Suspense

### Why?

The App Router includes React Canary releases, and the `browser` API now
provides a Suspense-based option for components that depend on browser
APIs such as `window` or `document`.

### How?

The guide calls `browser()` inside `use()` in a Client Component. It
then wraps that component in Suspense so prerendering can show the
closest fallback before the component renders in the browser.

### Testing

- `prettier --check docs/01-app/02-guides/single-page-applications.mdx`
- `alex docs/01-app/02-guides/single-page-applications.mdx --quiet`
(only existing SPA dictionary warnings)
2026-09-10 00:40:45 +02:00
Jiwon Choi 67f4215574 Enable React browser bailout by default (#98439)
Enable React browser bailouts by default without requiring applications
to opt in.
2026-09-09 15:04:01 -07:00
Will Binns-Smith 57a8476086 bundle-analyzer: record historical snapshots on each analyze run (#93520)
After each `next experimental-analyze` run, snapshot the output data
into a rolling `history/` directory so later builds can use it as a
comparison baseline.

Each snapshot gets a `metadata.json` with git branch/sha/dirty status,
Next.js version, and route count. A `history/history.json` index
(newest-first, capped at 20 entries) lets the UI list available
baselines without re-reading every snapshot directory. Old snapshots
beyond the cap are pruned automatically.

The client-side `SnapshotMetadata` type mirror lives in
`apps/bundle-analyzer/lib/snapshot.ts` so the compare UI can reference
it without a build-time dependency on the Next.js package.

<!-- NEXT_JS_LLM_PR -->

---------

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-09-09 14:11:28 -07:00
Aurora Scharff 9486750ff2 [evals] Cover focused Cache Components and Partial Prefetching cases (#97813)
## Summary

- update existing evals for synchronous I/O, instant shell validation,
and Cache Components configuration instead of adding duplicate fixtures
- add focused Partial Prefetching coverage for URL-data inclusion, the
prefetch stage, and the navigation stage
- retain focused Cache Components coverage for session isolation and
URL-dependent shells
- rebase onto current `canary` so the Partial Prefetching evals can use
the public `unstable_prefetch()` and `unstable_navigation()` API
references

## Coverage

The latest results use Claude Sonnet 4.6 for the coding agent and Claude
Haiku 4.5 for the agentic judge. The Partial Prefetching cases have been
rerun after the new API references landed, and the corrected Cache
Components criteria have also been rerun.

| Eval | Scenario | Baseline | Bundled docs |
| --- | --- | ---: | ---: |
| `agent-035-connection-dynamic` | Adopt Cache Components while keeping
synchronous request-time values fresh and stable navigation in the shell
| 5/5 | 5/5 |
| `agent-040-instant` | Follow default validation and keep a stable
product title ahead of live inventory | 4/4 | 4/4 |
| `agent-042-enable-ppr` | Translate legacy route configuration while
preserving hourly and request-specific behavior | 4/4 | 4/4 |
| `agent-048-prefetch-url-data` | Include cached URL-specific category
content in a selected per-link prefetch | 3/6 | 6/6 |
| `agent-049-prefetch-stage` | Exclude cached content from the App Shell
but include it in a selected prefetch | 1/5 | 5/5 |
| `agent-051-prefetch-until-navigation` | Prefetch a selected title
while deferring the rest until navigation | 0/5 | 5/5 |
| `agent-052-cache-components-session-isolation` | Reuse shared data
without placing member-specific request data in a public cache | 5/5 |
5/5 |
| `agent-053-cache-components-url-shell` | Keep URL data below a useful
shell and include every output-changing value in the cache key | 3/5 |
5/5 |

After the public API references landed, the prefetch-stage and
navigation-stage evals show the intended documentation signal. Baseline
Sonnet passed 1/5 criteria for `unstable_prefetch()` and 0/5 for
`unstable_navigation()`. With the canonical bundled-docs instruction,
Sonnet found the new references and passed all 5/5 criteria in both
cases. After clarifying that the URL-data navigation must not block and
linking the general navigation docs to the optimizing-prefetching guide,
the URL-data case improved from 3/6 without docs to 6/6 with bundled
docs.

## Result interpretation

- `agent-048` now passes 6/6 with bundled docs. The scoped documentation
link led the agent to the URL-data prefetch pattern instead of a
blocking render. Baseline Sonnet added `prefetch={true}` but relied on
the previous full-payload behavior and missed both the Partial
Prefetching configuration and cached URL data, resulting in 3/6.
- The low baseline scores for `agent-049` and `agent-051` are the
intended signal. Without the new references, the agents did not discover
the prefetch and navigation stages. With bundled docs, both cases pass
5/5.
- `agent-053` is also an intended documentation signal: the baseline hid
the useful shell behind a broad fallback, while bundled docs preserved
the shell and passed 5/5.
- `agent-035` now passes 5/5 in both conditions after removing an
unnecessary requirement for visible loading UI in one small metadata
region. `agent-052` also passes 5/5 in both conditions after removing
two failures for an omitted explicit `cacheLife()`, because the default
cache profile still applies, and clarifying for the judge that an
uncached component reading `cookies()` or `headers()` remains
request-specific without another opt-out marker.

## Overlap decisions

- `agent-035` now carries the synchronous-I/O adoption case, so the
duplicate `agent-051` fixture was removed.
- `agent-042` now carries the route-configuration migration case, so the
duplicate `agent-048` fixture was removed.
- `agent-034` remains focused on correct async `cookies()` and
`headers()` usage. `agent-052` tests the separate risk of leaking
request-specific values through a public cache.
- `agent-040` tests one meaningful shell under default validation.
`agent-041` still tests independent streaming across several dashboard
sections.
- `agent-048` intentionally brings cached URL-specific content into a
selected prefetch. `agent-053` tests the opposite shared-shell
requirement during Cache Components adoption.

## Verification

- Dry fixture generation passes for all eight cases in the table.
- `pnpm build-all`
- `agent-049-prefetch-stage`: baseline 1/5, bundled docs 5/5
- `agent-051-prefetch-until-navigation`: baseline 0/5, bundled docs 5/5
- `agent-035-connection-dynamic`: baseline 5/5, bundled docs 5/5
- `agent-048-prefetch-url-data`: baseline 3/6, bundled docs 6/6
- `agent-052-cache-components-session-isolation`: baseline 5/5, bundled
docs 5/5
- `pnpm exec tsc -p
evals/evals/agent-035-connection-dynamic/tsconfig.json --noEmit`
- `pnpm exec tsc -p evals/evals/agent-042-enable-ppr/tsconfig.json
--noEmit`

<!-- NEXT_JS_LLM -->
2026-09-09 22:19:32 +02:00
Luke Sandberg a8905ddfc2 Deflake gc_interrupt_is_self_healing (#98445) 2026-09-09 20:04:01 +00:00