731 Commits

Author SHA1 Message Date
Sebastian "Sebbie" Silbermann 43c1c60bca [16.3] [ci] Use OIDC tokens to read private preview builds (#97258)
Backports https://github.com/vercel/next.js/pull/96919

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-13 14:00:45 -07:00
Tim Neutkens e37ddd19f5 Fix deploy test TypeScript exclusions (#96545)
## Summary

Set `NEXT_PRIVATE_LOCAL_DEV=1` for directory fixtures in deploy mode,
matching the next-start setup. This keeps copied test runner files
excluded from TypeScript checks when deployment tests use
`tsconfig.test.json`.

## Verification

- `pnpm exec tsc --project test/lib/tsconfig.json --noEmit`
- Not run: deployment tests (requires Vercel deployment credentials)

<!-- NEXT_JS_LLM -->
2026-08-03 19:35:17 +00:00
Tim Neutkens cbf0cef687 Enable TypeScript CLI by default (#96497)
## Summary

Enable the project-local TypeScript CLI checker by default during `next
build`, while preserving `experimental.useTypeScriptCli: false` as an
opt-out to the TypeScript compiler API. Update the TypeScript 7
guidance, diagnostics, tests, and documentation to match the new
default.

## Verification

- `pnpm exec jest --runTestsByPath test/unit/isolated/config.test.ts`
- Pre-commit lint-staged checks passed (Prettier and ESLint)
- Not run: `pnpm test-start-turbo
test/production/app-dir/typescript-cli/typescript-cli.test.ts` (isolated
fixture dependency installation was blocked by unavailable npm registry
access)
- Not run: `pnpm --filter=next types` (repository has pre-existing
unrelated TypeScript errors)

<!-- NEXT_JS_LLM -->
2026-08-03 18:10:51 +02:00
dan be1424f3dc Make retry stop when the time it was given is up (#96354)
How long do you expect this to wait at worst (before failing)?

```js
await retry(async () => {
  expect(await browser.elementById('never-appears').text()).toBe('x')
}, 15_000)
```

After this PR, the answer is 15 seconds. (Well, technically, 15 seconds
+ whatever last attempt is, so 20.)

But before this PR, the answer was 3 minutes.

## What?

There's a bug (IMO) in `retry`. Instead of waiting for the time you give
it, it counts tries.

For example, `retry(fn, 15_000)` reads as "keep trying for 15 seconds".
However, what it actually was doing is divide 15000 by the 500ms
interval and try 30 times, sleeping 500ms in between every try.

This is a problem because **it wasn't measuring the stall time of each
try itself.**

When a try is cheap, it doesn't matter. But a try that looks for an
element in the browser isn't cheap: if the element isn't there, the
selector waits 5 seconds before giving up.

In the example above, each try sits in the selector for its full 5
seconds, and there are 31 of these intervals, plus the 15 seconds of
sleeping in between. This ends up waiting 170103ms, i.e. three minutes
for a wait that says 15 seconds.

Jest kills the test at its own timeout long before that, and the actual
error gets thrown away. What's left is `Exceeded timeout of 120000 ms`,
which doesn't say which assertion never came true. In the one I measured
it was `page.waitForSelector: Timeout 5000ms exceeded`. My agent lost an
hour trying to make sense of this twice now.

## Fix

We count time now. After the fix, the retry watches the clock and stops
when its time is up. It still always makes at least one try. The
interval no longer has to divide the duration, because nothing counts
steps any more. If the last try is slow, we're gonna wait for that last
try to end (so the timeout is still not *strictly* respected) so that we
can show the error in the correct test.

Cheap waits behave exactly as before. In my local run, a try that had to
repeat cost 251ms at the median, 459ms at the worst. So the only calls
this changes are ones that were already failing, and those now fail with
their own error instead of a test timeout. One flaky dev test that used
to die at 120 seconds with nothing to show now fails in 19.5 seconds
saying `Failed to retry within 15000ms (waited 16022ms)`, next to nine
tests that take 2.2 seconds each.

I added some regression tests. Since it's a test helper, a unit test
seemed fine.

## What this means for existing tests

The catch is that a call whose tries are slow now gets less time than it
used to, and this helper has 2110 call sites, so CI is the real check.
It found one file: the hash scroll tests in `navigation.test.ts` wait
with the default 3 seconds, and each try there costs 150-300ms on a
runner, so they were really getting about 5. They need more than 3, so
they failed on two dev shards. I gave them 10.

If this is green, we can merge it in isolation from the rest of the
stack.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:42:56 +01:00
Andrew Clark dfa7f4f713 Attempt static prefetch before resorting to runtime (#96095)
If we're reasonably confident that a segment can be prefetched
statically without omitting data that would have been included during a
runtime prefetch (e.g. cookies), the client should attempt prefetch the
segment statically instead of going straight to a runtime request.

The decision for whether to do this is based on the
ShouldAttemptStaticPrefetch added in previous steps.

If it turns out the static response is not sufficient, then it will fall
back to a runtime request.

This makes prefetching cheaper for pages that are fully statically
renderable. We can make the optimization better in the future with more
reliable per-segment computation of the ShouldAttemptStaticPrefetch, but
the current approach should at least work for fully static pages, which
is what's most important.

This optimization applies during both the Shell phase and the
Speculative phase of the prefetching algorithm.
2026-07-28 11:51:29 -04:00
Andrew Clark bdbbb1e887 Block prefetch task until sufficient response is received (#96017)
This is a refactor of the prefetching algorithm to block the prefetch
task from completing until the response is received.

Currently, a prefetch task is fire-and-forget: once a prefetch's
requests are spawned, the scheduler marks the originating task as
complete regardless of what the server ends up returning. However,
certain planned optimizations require us to verify the response from the
server to determine whether additional work is necessary. For example,
we may attempt to do a static prefetch instead of a runtime prefetch if
we optimistically assume that the static prefetch does not vary on
runtime data. We need to verify that assumption is correct before
marking the task as complete.

Now, whenever a pass over the task spawns a segment request, or
encounters an entry whose response hasn't arrived yet, the task
registers itself on the entry and exits as Blocked instead of Done. When
the entry resolves, the blocked task is pinged and the pass re-runs
against the received data. Only a pass that observes every response it
cares about advances the task to the next phase or completes it.

Rejected entries are handled per segment: a rejection counts as an
observed response, so the pass skips that segment and keeps prefetching
the rest. The task never registers on a rejected entry, since nothing
ever pings one. Retries are governed by the existing per-entry logic.

Also fixes a cache bug this exposed: when a revalidation response is
keyed at a more generic vary path than the stale partial entry that
prompted it (because the segment turned out not to vary on some param),
the stale entry shadowed the result on every lookup — the upgraded data
was unreachable and the revalidation was wasted. With blocking, that
dead end became an infinite loop: each fulfillment re-pinged the blocked
task, which re-read the same stale entry and spawned another
revalidation. Upserting (or re-keying) a segment now deletes settled
entries at more specific vary paths that the incoming entry supersedes.

The router-act test helper's "at least one request initiated" watchdog
is adjusted to account for the new blocking behavior: countable requests
can now be legitimately gated on an in-flight App Shell response (which
the watchdog intentionally doesn't count). On expiry it now proceeds to
response processing if the act scope intercepted a shell request, and
keeps waiting while shell requests are in flight elsewhere on the page,
instead of timing out.
2026-07-27 19:03:16 -04:00
dan e2dce63f3f [test] Unflake more tests (#96081)
- `detectContentType` test was too aggressive about asserting completion
in 1ms, let's give it several tries.
- We weren't correctly setting up pnpm overrides in tests, which breaks
things whenever canary comes out.
2026-07-27 20:54:10 +01:00
Hendrik Liebau 954eabec68 [refactor] Prepare dev validation for running on a worker thread (#96151)
This is a behavior-preserving refactor of the dev-mode Cache Components
validation, ahead of the change that moves it onto a worker thread.
In-process behavior is unchanged.

It narrows the context the validation functions require.
`runValidationInDev` and its helpers (`validateStaticShell`,
`warmupClientModulesForStagedValidation`, `validateStagedShell`,
`validateInstantConfigs`) now take a `ValidationRenderContext` (a `Pick`
of `AppRenderContext` plus the two `renderOpts` fields and the
debug-channel flag they actually read) instead of the full render
context, with `toValidationRenderContext` mapping the in-process and
build-time callers. The worker cannot reconstruct a full
`AppRenderContext` from a serialized snapshot, so narrowing the contract
to what validation genuinely consumes is what lets the same code run
there.

It also separates computing the validation errors from delivering them.
`runValidationInDev` now returns the errors instead of sending them to
the dev overlay inline, and the caller `runDevValidationInBackground`
delivers them via `logMessagesAndSendErrorsToBrowser`. The test-mode
lifecycle markers and delay move into `runWithDevValidationLogging`,
which brackets both the render and the delivery. When validation runs on
the worker, the worker computes the errors but the main thread still
delivers them (delivery needs the response object), so splitting the two
halves is a prerequisite.

Finally, it extracts the test-only validation lifecycle markers into a
shared `dev-validation-events` module. The `<VALIDATION_MESSAGE>`
wrapping and the event shape had been hand-rolled at each emission site
in `app-render.tsx` and duplicated again in the test harness; they now
live in one place as `formatValidationEvent` and an exported
`ValidationEvent` union that the in-process emitters call and the test
utilities import rather than redeclare. The worker emits the same
markers through the same helper once it is added, so a single definition
keeps every producer and the test parser in sync. Build validation's
marker `requestId`, previously the numeric `Date.now()`, is stringified
to fit the shared `string` type; it appears only in the logged marker
payload, which nothing matches on, so this too is unobservable.
2026-07-25 07:21:02 +02:00
Benjamin Woodruff e630d81822 [ci] Enforce minimumReleaseAge in e2e tests that install external dependencies (#95628)
We also mitigate the security impact of e2e tests by running everything
in VMs, and by not reading caches in release jobs, but we should still
be using `minimumReleaseAge` in e2e tests.

Ideally we'd also use lockfiles here too, and I'll work on that next,
but that is likely a lot more complicated because we probably need some
way to merge lockfiles, since we can't pin the next.js and react
versions in every test, and we need a reasonable/efficient way to
bulk-update all the lockfiles for tests that need it.

Depends on https://github.com/vercel/next.js/pull/95668 for a bunch of
windows path representation fixes that this ends up exposing in CI,
because adding workspace files causes pnpm to change the absolute path
representation used in the Windows junction points that it creates.
2026-07-21 14:54:01 -07:00
Vercel Release Bot cee9824aa5 Upgrade React from 172742b4-20260716 to 81e442ea-20260721 (#96016)
Co-authored-by: next-js-bot[bot] <279046576+next-js-bot[bot]@users.noreply.github.com>
2026-07-21 17:25:13 +00:00
Tim Neutkens 81d0e8519d Fix instant validation blocking navigations (#95939)
### What?

- Run development instant validation only after the navigation response
has finished.
- Let a newer request supersede stale validation work and yield between
validation render attempts.
- Forward the cancellation signal into React validation prerenders so
asynchronous validation work can stop promptly.
- Add focused scheduler/response tests and assert that development
validation starts after the response finishes.

### Why?

Instant validation was detached from the response promise, but its
additional React renders still ran on the same Node.js event loop. A
subsequent navigation could therefore wait hundreds of milliseconds for
stale diagnostic work from the previous request.

Instant validation still runs when the server is idle. When another
navigation arrives, foreground request work now takes priority and the
obsolete validation is discarded.

### How?

Each Cache Components development request receives a validation
generation signal. Starting a newer request aborts the previous
generation. Validation waits for the Node response `finish` event,
yields through the event-loop poll phase between attempts, and combines
the generation signal with React's existing prerender abort signals.

Tests:

- `pnpm --filter=next build`
- `pnpm --filter=next types`
- `pnpm exec jest --runTestsByPath
packages/next/src/server/app-render/dev-validation-scheduler.test.ts
packages/next/src/server/app-render/wait-for-response.test.ts`
- `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo
test/e2e/app-dir/instant-validation/suspense-boundaries.test.ts -t
"valid - static prefetch - suspense around runtime and dynamic"`

<!-- NEXT_JS_LLM_PR -->
2026-07-21 09:53:43 +02:00
Benjamin Woodruff 390eff3b86 [ci] Share a single browser instance across all test suites in a single job (#95589)
The idea here is that we waste a bunch of CPU setting up each test suite
by spawning an entirely new Chrome process, but playwright can share the
same browser across many tests. Tests are still isolated at the browser
level, just not at the OS process level.

Hoping to see some performance improvement in e2e tests. Compare to:
https://github.com/vercel/next.js/pull/95617

Somewhat unscientific (single sample) results:

```
Comparing the head-commit build-and-test runs — PR #95589 run [28981702219 ](https://github.com/vercel/next.js/actions/runs/28981702219)vs PR #95617 run [28981686692](https://github.com/vercel/next.js/actions/runs/28981686692). Both ran 101 jobs and both succeeded.

  ┌─────────────────────────┬────────────────┬───────────────────────┬───────────────────┐
  │         Metric          │ #95617 control │ #95589 shared browser │       Delta       │
  ├─────────────────────────┼────────────────┼───────────────────────┼───────────────────┤
  │ Raw wall-time sum       │ 616.2 min      │ 571.2 min             │ −45.0 min (−7.3%) │
  ├─────────────────────────┼────────────────┼───────────────────────┼───────────────────┤
  │ Billable (ceil per job) │ 658 min        │ 617 min               │ −41 min (−6.2%)   │
  └─────────────────────────┴────────────────┴───────────────────────┴───────────────────┘

  The shared-browser experiment is cheaper, and the savings land almost exactly where you'd expect — the browser-driven test suites — while non-browser jobs (rust, lint, unit, windows) are flat within noise:

  ┌─────────────────────────────────┬─────────┬────────┬───────┐
  │          Job category           │ control │ shared │ delta │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ test prod                       │   130.7 │  110.5 │ −20.2 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ cache components dev            │    51.5 │   42.9 │  −8.7 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ turbopack dev                   │    75.1 │   69.5 │  −5.5 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ turbopack production            │    86.3 │   81.1 │  −5.2 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ test dev                        │   110.4 │  105.5 │  −4.9 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ cache components prod           │    48.7 │   49.0 │  +0.3 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ firefox and safari              │     5.3 │    7.3 │  +2.0 │
  ├─────────────────────────────────┼─────────┼────────┼───────┤
  │ flake-detection jobs (combined) │     ~15 │    ~21 │   +~6 │
  └─────────────────────────────────┴─────────┴────────┴───────┘

  Takeaways:
  - Net savings of ~41 billable minutes per run (~6%), concentrated in the Chromium-driven test prod/dev, turbopack, and cache components suites — consistent with reusing one browser process instead of spawning per-suite.
  - The small regressions are in firefox and safari and the "new/changed tests for flakes" jobs (+~8 min combined). Worth a glance, though they may just be run-to-run variance.

  Caveat: this is a single run per PR, so there's real variance run-to-run. That said, the fact that the deltas track the browser-heavy jobs specifically — and not the Rust/lint/unit jobs — is a good signal the effect is genuine rather than noise. If you want a firmer number, re-running each PR 2–3× and averaging would tighten it up.
```
2026-07-21 01:36:10 +00:00
Niklas Mischkulnig 756d6dc9ea Move immutable static assets config option out of experimental (#95351)
Keep reading `config.experimental.supportsImmutableAssets` for now to
not break existing adapters until they are migrated.

Closes NAR-868
2026-07-20 15:35:14 +02:00
Sebastian "Sebbie" Silbermann 3ae5630d0a [ci] Allow running all deploy tests with builds from a private registry (#95784) 2026-07-16 11:33:21 +02:00
Niklas Mischkulnig 1a7e6bda0f chore: Remove stale build warning (#95813)
Added in https://github.com/vercel/next.js/pull/88001
But it doesn't actually work. It thinks the build is always stale
2026-07-15 18:40:46 +00:00
Benjamin Woodruff 337a40c672 [ci] Pin typescript version in tests (#95619)
Typescript 7 came out, which is breaking a lot of our e2e tests.
2026-07-08 14:12:48 -07:00
Niklas Mischkulnig da06078b9b test: Fix immutable static asset deployment tests for real (#95600)
https://github.com/vercel/next.js/pull/95550 wasn't enough:

-
https://github.com/vercel/next.js/actions/runs/28907225463/job/85769093204
-
https://github.com/vercel/next.js/actions/runs/28907225463/job/85769093252

Because `VERCEL_IMMUTABLE_STATIC_FILES_ENABLED` might be set, but it
still only works when using adapters, so it broke in the non-adapter
deploy test jobs.

Also prepare for when we switch the default of `NEXT_ENABLE_ADAPTER` by
setting it explicitly to `0` for when we want to test the old CLI
builder.
2026-07-08 14:16:35 +02:00
Hendrik Liebau 60ddf0966a Cancel a superseded Server Components HMR refresh's server-side work (#95486)
When rapid edits overlap Server Components HMR refreshes in `next dev`,
only the newest refresh can commit. The client already aborts a
superseded refresh's fetch, which closes its response. We use that
response-close to stop the server work the superseded refresh started,
so the dev server no longer runs a render (and, under Cache Components,
a validation) whose result is discarded.

When the `serverComponentsHmrCancellation` flag is enabled and the
request is an HMR refresh over a Node response, we derive an abort
signal from `signalFromNodeResponse(ctx.res.originalResponse)` and
thread it into the Flight render. This covers both dev RSC render paths:
the Cache Components staged render in
`generateDynamicFlightRenderResultWithStagesInDev` and the
non-Cache-Components render in `generateDynamicFlightRenderResult`.

The render is aborted through `renderToNodeFlightStream`, which calls
`abort()` on the pipeable that `renderToPipeableStream` returns when the
signal fires. React's Node Flight API has no `signal` option, unlike the
Web `renderToReadableStream`, aborting the returned pipeable is the
intended way to stop the render. This also removes the incorrect
`signal` field from the `renderToPipeableStream` type declaration and
derives `FlightRenderOptions` from `renderToReadableStream`'s options,
so the render wrappers are no longer typed as `any`.

Under Cache Components the superseded refresh additionally skips its
detached validation, which only runs on the staged render, so it never
validates a discarded tree, and any error thrown while its background
renders tear down is swallowed. The aborted Flight renders surface as
abort errors that `createReactServerErrorHandler` already ignores, so
nothing reaches the error overlay or the CLI.

The `hmr-rsc-cancellation` suite exercises both render paths, with Cache
Components controlled by the `__NEXT_CACHE_COMPONENTS` test shard rather
than the fixture config. A child component logs when React renders it,
and the test asserts the superseded refresh never logs, since its render
is aborted before reaching the child, while the committed refresh does.
The detached-validation assertions run only when Cache Components is
enabled.
2026-07-07 21:53:00 +02:00
Niklas Mischkulnig 8b5df172e4 test: Fix immutable static asset deployment tests (#95550)
1. The env var ended up having a different name than I foresaw months
ago. Now `next.assetToken` is correctly `undefined` if the deployment
used immutable static assets.
2. `expect(searchParams.get(...)).toBe(undefined)` was failing because
`.get()` returns null for unknown keys

Failures:
-
https://github.com/vercel/next.js/actions/runs/28831699621/job/85515363446
-
https://github.com/vercel/next.js/actions/runs/28831699621/job/85515363310
2026-07-07 16:53:46 +02:00
Hendrik Liebau cd0aee96d7 Re-query deploy-test build logs until id markers appear (#95243)
Deploy tests recover the `BUILD_ID`, `DEPLOYMENT_ID` and
`NEXT_SUPPORTS_IMMUTABLE_ASSETS` values by scraping them out of the
deployment's build logs, where the test fixture's `post-build` script
prints them as the final lines of the build. We fetch those logs with
`vercel inspect --logs` after `vercel deploy` returns, but a deployment
can report `Ready` before its full build-log tail has propagated to the
log query API, so a single call sometimes returns a truncated prefix
that stops before the markers. When that happens `parseIdsFromCliOuput`
throws "Failed to get buildId from logs" and fails the whole suite
during setup, which is the intermittent failure we have been seeing
across the deploy jobs.

This change wraps the log fetch in `fetchBuildLogsUntilComplete`, which
re-queries `vercel inspect --logs` until the last-printed marker
(`NEXT_SUPPORTS_IMMUTABLE_ASSETS`) appears before parsing, bounded to
twenty attempts three seconds apart. Gating on the final marker rather
than the first ensures a partial read cannot slip past the loop into the
parser. Streaming the logs from `vercel deploy --logs` as part of the
deploy itself was considered, but it has the same race: the CLI stops
streaming once the deployment reports `Ready`, before the `post-build`
tail flushes, and a single stream cannot re-query for the missing lines.
2026-07-01 21:08:58 +02:00
Janka Uryga c131314bcf [PP] Validate Shell prefetches (except gSP) (#95151)
Implements instant validation for `partialPrefetching`. In this mode,
`<Link>` prefetches an App Shell, which cannot access link data, and we
need to warn for that.

The changes in `instant-validation.tsx` are relatively simple: for an
App Shell, we simply use `ShellRuntime` for all the new segments. We
might also force them into `Runtime` for the purposes of discriminating
dynamic holes. If a hole is present in `ShellRuntime` but disappears in
`Runtime`, then we know it's caused by **link data** (as opposed to
runtime or dynamic data). I've added some new error messages for this
case.

Note that the implementation here is incomplete: it uses the chunks from
the dev render, which resolves static params in the `Static` stage. We
use `ShellRuntime` for validating the App Shell, so as a result, static
params are incorrectly included in it and don't trigger link data
errors. This will be implemented in a follow-up.

Note: It seems like we have some pre-existing bug in build validation
where `fallbackParams` aren't populated, so params resolve statically
when they shouldn't. I've marked two tests with `// TODO(app-shells):
missing fallback params in build validation` so we can follow up and fix
those.
2026-07-01 02:36:41 +00:00
Sebastian "Sebbie" Silbermann dcb662c7fb [ci] Add support for auth based preview build registries (#95204)
Follow-up to https://github.com/vercel/next.js/pull/93464 which didn't
consider auth based preview build registries.
2026-06-30 20:59:15 +02:00
Hendrik Liebau 6771a60dfc [test] Recover from a leftover build process on test retry in build() (#95304)
PR #94797 taught `NextStartInstance.start()` to recover from a leftover
`next build` process left behind when a previous test attempt was
interrupted mid-build by the per-test timeout, but `build()` kept
throwing `can not run export while server is running` unconditionally
whenever a child process was still tracked. Tests that only call
`next.build()` (and never `start()`) therefore still failed their
`jest.retryTimes` retry with that misleading error, masking the original
timeout.

This extracts the recovery logic into a shared
`stopLeftoverBuildOrThrow` helper and routes both `start()` and
`build()` through it. When a leftover `next build` process from an
interrupted attempt is found, it is stopped so the retry can continue
from a clean state; a genuinely running server still throws, preserving
the existing guard. Sharing one helper keeps the two call sites in sync.
2026-06-30 14:00:47 +02:00
Hendrik Liebau 60e4f3061f Avoid re-logging server-originated errors forwarded from the browser (#94917)
During `next dev`, certain errors (such as Cache Components validation
errors) are logged directly to the terminal by the dev server and are
also sent to the browser so they can be shown in the dev overlay. The
browser logs them to its own console, and the browser-to-terminal log
forwarding would replay that copy back to the CLI, so the same error
appeared in the terminal twice.

This change marks those errors when the browser receives them, in the
`ERRORS_TO_SHOW_IN_BROWSER` handler, using a non-enumerable symbol.
`forwardErrorLog`, the single function all forwarded error logs pass
through, then skips any error carrying that marker, since it already
originated on the server and was logged there. The marker has to be set
browser-side because it wouldn't survive RSC serialization, which is the
same reason the error code is sent through a side-channel map rather
than on the error itself.

The error still shows in the browser console and the dev overlay; only
the redundant terminal echo is suppressed. This applies to every error
delivered over the errors RSC stream, all of which are logged on the
server before being sent, so the forwarded copy is always a duplicate.

> [!TIP]
> Review the two commits individually to see the before/after change.
2026-06-17 22:39:20 +02:00
Hendrik Liebau 96e9a8e246 Make cacheMaxMemorySize: 0 and custom cache handlers fast in dev (#94784)
When Cache Components is enabled, `next dev` treats a `'use cache'`
value as a miss and renders as if the cache were empty whenever the read
does not resolve right away. Two development configurations triggered
that even for values that were already cached, so warm reloads streamed
slowly instead of serving the cached value: `cacheMaxMemorySize: 0`
replaced the built-in default handler with a no-op stub, so nothing was
cached at all, and custom cache handlers with a slow or remote `get` did
not return in time.

For the size-0 case, development now uses a real in-memory handler
instead of the no-op stub, and the `'use cache'` wrapper forces a
dynamic cache life (`revalidate: 0`, a 5-minute `expire`) for it, the
same treatment private caches already receive, so every read serves the
stale entry and re-warms a fresh one in the background. This also fixes
the dev private handler, which was sized from `cacheMaxMemorySize` and
so degraded to the no-op stub whenever `cacheMaxMemorySize: 0` was set.

Custom cache handlers keep their configured cache life, since their
backing owns it. Instead we put a fast built-in in-memory front handler
in front of the configured one through the new `TieredCacheHandler`,
which serves warm reads from the front, writes through to both tiers,
and reconciles the front against the backing in the background, evicting
the front entry when the backing no longer has it (the handler interface
has no per-key delete, so it overwrites the entry with an
already-expired copy). These dev-only handlers are kept out of the
registered handler set and merged in only where tag operations iterate,
so `revalidateTag` still reaches them.

Everything is gated on `process.env.__NEXT_DEV_SERVER`, so production is
unchanged: `cacheMaxMemorySize: 0` still caches nothing, private entries
are still never persisted, and configured handlers are used directly.
New development test suites cover the size-0 and custom-handler
behavior.
2026-06-16 18:06:28 +02:00
Hendrik Liebau 7b00d9f775 [test] Recover from a leftover build process on test retry (#94797)
When a `next start` test exceeds the per-test timeout while `next build`
is still running, the build process is left running and tracked by the
next instance, because the build phase of `NextStartInstance.start()`
clears `childProcess` only when the process exits. `jest.retryTimes`
then re-runs the test body in the same process, and `start()`
immediately throws `next already started`, which masks the original
timeout.

`NextStartInstance` now tracks whether a build or a server currently
owns the child process. When `start()` finds a leftover `next build`
process from an interrupted attempt, it stops that process and continues
instead of throwing, so the retry can start from a clean state. The
`next already started` guard is preserved for the case where a server is
genuinely already running.
2026-06-16 09:06:46 +00:00
Andrew Clark f76af5ca6e Make router-act ignore App Shell prefetch requests (#94580)
When App Shells is enabled, prefetching is split into two phases: a
prefetch for the shell, and a separate phase for the per-page content.

This would cause a handful of the existing prefetch-related tests to
fail once App Shells is turned on. However, the fact that the shell is
prefetched separately from the page content is an incidental detail of
how prefetching works. The App Shell is conceptually part of the route,
not part of the "prefetch". Or in other words, App Shell prefetching has
more in common with incremental module loading than it does with
prefetching in the traditional sense used in the context of web apps.

So, this updates router-act to ignore App Shell requests for the
purposes of making assertions about prefetch responses. They're still
captured, fulfilled, and awaited — they just don't participate in the
matching logic. Tests that specifically assert on App Shell behavior can
opt back in with `createRouterAct(page, { includeAppShellRequests: true
})`.

This is groundwork for the next PR, which enables App Shells by default.
With these requests ignored, most prefetch tests pass unchanged
regardless of whether App Shells is on.

<!-- NEXT_JS_LLM_PR -->
2026-06-09 10:37:59 -07:00
Vercel Release Bot 84f9247617 Upgrade React from f0dfee38-20260529 to 43bcbf80-20260603 (#94440)
[diff
facebook/react@f0dfee38...43bcbf80](https://github.com/facebook/react/compare/f0dfee38...43bcbf80)

<details>
<summary>React upstream changes</summary>

- https://github.com/facebook/react/pull/36586
- https://github.com/facebook/react/pull/36603
- https://github.com/facebook/react/pull/36585
- https://github.com/facebook/react/pull/36580
- https://github.com/facebook/react/pull/36584
- https://github.com/facebook/react/pull/36583
- https://github.com/facebook/react/pull/36576
- https://github.com/facebook/react/pull/36575
- https://github.com/facebook/react/pull/36574

</details>

---------

Co-authored-by: next-js-bot[bot] <279046576+next-js-bot[bot]@users.noreply.github.com>
Co-authored-by: Josh Story <gnoff@storyposted.com>
2026-06-05 08:11:55 -07:00
Hendrik Liebau abbfa7e4c2 Handle a purged browser cache on back navigation (#94317)
When the browser's HTTP cache entry for a back-navigation target has
been evicted between forward visit and back-press — for example with a
long-lived tab, storage pressure, or a manual cache clear — the browser
re-fetches the document fresh from the server. Previously we relied on
`type === 'back_forward'` alone to decide that the document came from
cache, which meant we treated those re-fetches as cache restores too.
The persisted-chunk lookup would miss on the fresh response, and we'd
recover with an unnecessary `location.reload()`.

The same edge case is exposed by the Playwright/WebKit combination in
the upstack PR: bfcache isn't utilized there, so back-navigations always
come over the network, and the old single-signal check classified those
re-fetches as served-from-cache and triggered the same unnecessary
reload.

With this change we split the cache-restore detection into two phases.
At script-execution time we look at `deliveryType` (Chrome ≥109, Safari
≥17) and the navigation entry's `transferSize`/`encodedBodySize` to
decide cache-restore vs fresh-response when those fields are populated.
When the body bytes haven't been measured yet — the common case for
streaming Next.js dev RSC responses on every browser, and also WebKit
leaving both size fields at zero at exec time — we suspend the readable
until `pageshow` and re-check there. On a fresh re-fetch we route
through the live WebSocket-backed channel, which already has the debug
data for the new response, and skip the reload entirely.

The new `bfcache-regression` test exercises this edge case on Chromium
by clearing the browser cache via CDP between the forward and back
navigations, using a new `clearBrowserCache()` helper on the test
browser wrapper. The same exec-time code path is naturally hit by Safari
whenever its navigation entry's size fields are still zero at
script-execution time, but the harness can't force the eviction
deterministically there.
2026-06-02 14:46:02 +02:00
Tim Neutkens 765b39c410 Prerender static metadata under dynamic segments to canonical pathname (#93873)
### What?

Fixes an invariant failure (`failed to find source route
/dynamic/[id]/apple-icon.png for prerender
/dynamic/[id]/apple-icon.png`) raised by the build adapter when an app
contains static metadata files (`apple-icon.png`, `icon.png`,
`opengraph-image.png`, `twitter-image.png`, `sitemap.xml`) under a
dynamic segment.

### Why?

Static metadata files under a dynamic segment were treated as full
dynamic prerenders: they ended up in `prerenderManifest.dynamicRoutes`
with `fallbackRouteParams`, but no `.body` file was exported and they
never received an `appOutputMap` entry. The adapter's `dynamicRoutes`
loop then asked `getParentOutput` for a source route that didn't exist
and threw.

These files are content-stable across params — the route handler returns
the same bytes for every URL that matches the dynamic pattern — so they
should prerender once under a canonical pathname rather than be treated
like per-param dynamic routes.

### How?

- New `getStaticMetadataPrerenderPathname` helper that maps
`/dynamic/[id]/apple-icon.png` to `/dynamic/-/apple-icon.png`. This is
the canonical pathname already used by `fillStaticMetadataSegment` for
HTML link emission.
- In `build/utils.ts`, route `APP_ROUTE` entries that are static
metadata files through a new `buildStaticMetadataStaticPaths` (one
prerender path, no fallback params) instead of the regular
`buildAppStaticPaths`.
- In `build/index.ts`:
- Rewrite each prerendered static metadata route to its `-`-placeholder
pathname before splitting into known/unknown buckets, so it lands in
`prerenderManifest.routes` with a `.body` file.
- Skip static metadata entries in the `dynamicPrerenderedRoutes` loop so
they don't get a stray `PRERENDER` manifest entry.
- Defensive sweep before write: drop any leftover bracketed metadata
keys from `prerenderManifest.dynamicRoutes`.
- In the adapter, look up `prerenderManifest.routes` under the canonical
placeholder pathname (with locale prefix when applicable) and skip
static metadata in the `dynamicRoutes` loop.
- Unit tests in `get-metadata-route.test.ts` lock in the placeholder
behavior for the tricky cases: catchall, optional catchall, multi-level
dynamic, literals between dynamics, dynamic + catchall mix.

Reproduction:
`test/e2e/app-dir/metadata-static-file/metadata-static-file-dynamic-route.test.ts`
configured with `adapterPath` previously crashed at
`handleBuildComplete`; all three cases in that file now pass.

Runtime stays the same: arbitrary param URLs continue to be served by
the dynamic route handler shipped with the app route; only the canonical
`-` URL is statically served.

<!-- NEXT_JS_LLM_PR -->
2026-05-22 19:49:04 +02:00
Tim Neutkens e860cec656 test: migrate webdriver callers to next.browser (#93941)
### What?

Migrate remaining direct `next-webdriver` test callers that have a
`NextInstance` to `next.browser()`, and expose the shared `Playwright`
browser type from `e2e-utils`.

### Why?

`NextInstance.browser` should be the supported browser-opening interface
for test fixtures, with `next-webdriver` kept as the private
implementation detail.

### How?

Updated affected development, e2e, and production tests to call
`next.browser()` directly, passing `baseUrl` where tests intentionally
target a manually spawned or proxied server. Shared helpers now receive
browser callbacks from the test context, and browser types import
`Playwright` from `e2e-utils` instead of deriving from `next.browser` or
importing from private paths.

<!-- NEXT_JS_LLM_PR -->
2026-05-22 14:01:58 +02:00
Tim Neutkens 7da98e1318 Convert more tests from createNext -> nextTestSetup (#93799)
## What?

Converts more tests that use `createNext` to `nextTestSetup`

Follow-up to #93767

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 09:51:36 +02:00
Yavor Punchev 495c3183f8 Redesign dev overlay: cleaner shell + instant fix-card guidance (#93755)
## Summary

### Error overlay shell
- Clean up nav, dialog frame, footer placement, and button styles across
the overlay
- Remove unused `fader` and notch components
- Tighten layout spacing throughout the dialog

### Instant error guidance (fix cards)
- New data-driven fix-card grid for instant errors: each card has a
`group` (Stream / Cache / Block / Static / Dynamic / Client / Defer /
Measure / Prerender), an icon, a color, and an optional docs link
- Whole card is clickable when a docs link is present, with an
always-visible external-link indicator
- Centralized group metadata (label / color / icon) in
`instant-guidance-data.ts` so cards stay consistent across the overlay
and the docs surface
- Outlined SVG icons live in a dedicated `fix-card-icons.tsx`
- Snippets refined: cache cards trimmed to 3 lines, client sync-IO defer
cards use block-body arrow syntax so longer expressions don't overflow

### Code frame
- Restore the red bar / caret on the errored line (parser previously
only matched storybook synthetic frames)
- Fix caret alignment with `codeFrameColumns` output
- Style inline error code snippets

### Storybook & examples
- Add error examples and instant-error fixtures for development/testing
- Align all 11 instant storybook fixtures with canary factory output and
the matching test-app routes

---------

Co-authored-by: Aurora Scharff <aurora.sofie@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com>
2026-05-15 23:25:54 +02:00
Tim Neutkens 5c283f596b Fix React 18 tests (#93763)
## What?

Follow-up to #93247. There was a failure on the React 18 tests. Missed
in #93247 as they only run on `canary`.
2026-05-11 21:31:40 +02:00
Tim Neutkens 8141dcf12e Convert test/integration to isolated tests (#93247)
### What?

Converts every test under `test/integration/` to an isolated test
running through `nextTestSetup` (under `test/e2e/`, `test/production/`,
`test/development/`, or `test/unit/`), then deletes `test/integration/`
along with the legacy CI orchestration that was specific to it.

- `test/integration/` removed entirely (~327 test suites)
- New isolated suites added across the existing folders:
  - `test/e2e/` — 175
  - `test/production/` — 130
  - `test/development/` — 43
  - `test/unit/` — 1
- `.github/workflows/build_and_test.yml` and `run-tests.js` no longer
have any `integration` branches
- `nextTestSetup` gained a `baseUrl` option on `next.browser()` so a
small number of tests that drive their own proxy/static-export server
can keep using `next.browser(...)` instead of importing `next-webdriver`
directly

### Why?

`test/integration/` predated `nextTestSetup` and ran tests directly
against the source checkout via custom helpers (`launchApp`,
`nextBuild`, `nextStart`, `runNextCommand`, `webdriver`, `fetchViaHTTP`,
…). Each suite hand-rolled its own dev/start/build orchestration,
fixture mutation, and process management.

The isolated test model used by the rest of the repo gives each suite an
isolated working directory containing a packed `next.tgz` install, a
uniform `next.start()` / `next.build()` / `next.fetch()` /
`next.browser()` API, and the same lifecycle for dev, start, and deploy
modes — so a single set of assertions covers all three. Deploy-mode
skips and per-feature gates are expressed declaratively
(`skipDeployment`, `disableAutoSkewProtection`, `if (skipped) return`)
instead of branching on `process.env`.

Removing `test/integration/` lets us:

- Delete the bespoke orchestration code in the CI workflow and
`run-tests.js`
- Run every converted suite consistently in dev, start, and deploy modes
(where applicable)
- Reproduce every test locally with the same `pnpm
test-{dev,start}-{turbo,webpack}` commands; no separate `integration`
path
- Open the door to running `test/production` against deployments in the
future (the converted suites already declare `skipDeployment` so they
can be flipped on)

### How?

Mechanical conversion per suite, with targeted clean-ups:

1. **Per-suite conversion.** Each
`test/integration/<name>/test/index.test.{js,ts}` was rewritten into a
single `<name>.test.ts` under the right folder based on what the
original exercised:
   - `launchApp` / dev-only assertions → `test/development/`
- `nextBuild` + `nextStart` / start-only assertions → `test/production/`
   - Both → `test/e2e/`
- The one pure jsdom render check (`link-without-router`) → `test/unit/`

2. **API mapping.** Custom helpers were replaced by `nextTestSetup`
equivalents: `launchApp` → `next.start()`, `nextBuild` → `next.build()`,
`runNextCommand` → `next.runCommand`, `fetchViaHTTP` → `next.fetch`,
`webdriver(...)` → `next.browser(...)`. Fixture mutations switched from
raw `fs.writeFile`/`fs.rename` to `next.patchFile` (with the 3-arg
`runWithTempContent` callback when the change has a defined scope) and
`next.deleteFile`.

3. **Deploy-mode handling.** Suites that can't run in deploy mode (use
`patchFile` / `next.build()` / depend on local CLI output) declare
`skipDeployment: true` and early-return on the `skipped` boolean. Suites
where Vercel's edge mutates URLs (`&dpl=`, immutable assets) declare
`disableAutoSkewProtection: true`.

4. **`next.browser({ baseUrl })`.** A handful of tests
(`prerender-export`, `cdn-cache-busting`, `preload-viewport`, both
`react-virtualized` suites) need to drive a separate server (a
static-export server or an `http-proxy` instance) rather than the
Next.js process. Instead of importing `next-webdriver` directly, those
tests now pass `{ baseUrl: <port|url> }` to `next.browser()`. For the
proxy cases, the proxy was moved into `server.js` inside the fixture and
`http-proxy` declared via the `dependencies` option of `nextTestSetup`,
so the test runs with a fully isolated dependency graph.

5. **CI clean-up.** With `test/integration` gone, the `test
integration*` jobs and `integration-tests-manifest`-related logic in
`.github/workflows/build_and_test.yml` were removed, and `run-tests.js`
no longer has the `integration` test-folder branch.

6. **Validation.** The PR was iterated against multiple full CI runs;
the remaining failures on the latest run are pre-existing flakes
(segment-cache 60s `act` timeouts in turbopack-prod) or transient
infrastructure issues unrelated to the conversion.
2026-05-11 13:55:47 +02:00
Sebastian "Sebbie" Silbermann 225be6afd8 [test] Pin package manager versions used (#93592) 2026-05-07 15:01:25 +02:00
Vercel Release Bot bab166a0d2 Upgrade React from f4e0d4ed-20260429 to dd453071-20260506 (#93547)
Co-authored-by: next-js-bot[bot] <279046576+next-js-bot[bot]@users.noreply.github.com>
2026-05-06 18:19:56 +00:00
Hendrik Liebau 2dece0e948 Fix deploy tests crashing under AI coding assistants (#93394)
The Vercel CLI bundles `@vercel/detect-agent`, which inspects
environment variables that AI coding assistants (Claude Code, Cursor,
Codex, Gemini CLI, GitHub Copilot CLI, and others) set in every shell
they spawn. When the CLI detects one, `vercel deploy` writes a JSON
payload to stdout instead of the plain deployment URL. The harness then
crashes when `new URL(deployRes.stdout)` in `next-deploy.ts` runs, which
breaks every `test-deploy*` run launched from such a shell.

The fix appends `--non-interactive=false` to `vercelFlags` so it is
passed to both `vercel link` and `vercel deploy`. The CLI honors that
flag as an explicit override of the agent-derived default, and the
deploy command's JSON-vs-plain output decision is gated on
`client.nonInteractive` (`const asJson = formatResult.jsonOutput ||
client.nonInteractive` in the deploy source), so a single flag is enough
to restore plain-URL stdout.

The alternative considered was reading `deployment.url` out of the JSON
payload at the single call site that breaks. That is a more localized
change but couples the harness to an undocumented payload shape that
exists only for AI agents, so a future rename of that field would
silently break deploy tests again. Forcing the CLI's output mode avoids
that coupling, which is why the flag won.

With this change, `pnpm test-deploy` and `pnpm test-deploy-turbo` work
uniformly whether they are launched from a human's interactive terminal
or from inside an AI coding assistant.
2026-04-30 22:04:29 +02:00
Sebastian "Sebbie" Silbermann 8adb4ea838 [test] Fix broken terminal output (#93357)
Nesting TUIs is hard but also a bit excessive since Turborepo's TUI
isn't interactive if `stdin` is ignored.

Co-authored-by: Copilot <copilot@github.com>
2026-04-29 12:09:11 +00:00
Sebastian "Sebbie" Silbermann 8ff558ef5a [test] Suppress build when preparing next for cache hits (#93355)
In https://github.com/vercel/next.js/pull/92575 we started using
Turborepo to build a fresh copy of Next.js before each test. This can
become quite noisy IMO since Turborepo replays logs by default. Now we
hide logs for cache hits.

If you want to debug logs from cached builds, you should use `pnpm
build` directly.


```terminal
@next/polyfill-nomodule#build > cache hit, suppressing logs 951e3e04258a486d 
 @next/playwright#build > cache hit, suppressing logs a37fc1279a93411b 
 @next/routing#build > cache hit, suppressing logs e29bf8207712d9ea 
 @next/env#build > cache hit, suppressing logs 6f68cdf7c8f202f6 
 @next/bundle-analyzer-ui#build > cache hit, suppressing logs 5dbf9112c4f7029e 
 @next/polyfill-module#build > cache hit, suppressing logs 601b6f5afa47853d 
 @vercel/devlow-bench#build > cache hit, suppressing logs dfd1c037ef4e8100 
 @next/codemod#build > cache hit, suppressing logs a5eac293134b4263 
 @next/react-refresh-utils#build > cache hit, suppressing logs 51d64c1f7e8e5cfb 
 @next/eslint-plugin-next#build > cache hit, suppressing logs 74ec27fec9910c71 
 create-next-app#build > cache hit, suppressing logs 0c4c9d38e0e0913e 
 @next/font#build > cache hit, suppressing logs d1d057c5ffe70285 
 @next/bundle-analyzer#pack-for-isolated-tests > cache hit, suppressing logs 867b1e9082440087 
 @next/env#pack-for-isolated-tests > cache hit, suppressing logs 50d7a018277cafb8 
 next-rspack#pack-for-isolated-tests > cache hit, suppressing logs 1046e9cfa3ba6879 
 @next/mdx#pack-for-isolated-tests > cache hit, suppressing logs 6da45b672b5b081a 
 eslint-config-next#build > cache hit, suppressing logs fcf529c5b1428c5d 
 @next/font#pack-for-isolated-tests > cache hit, suppressing logs 9675f7ed199b9841 
 next#build > cache hit, suppressing logs 8c62df3806949bad 
 @vercel/turbopack-next#build > cache hit, suppressing logs 94890b06e4b937bf 
 @next/third-parties#build > cache hit, suppressing logs f1da8c56f9222a0c 
 next#pack-for-isolated-tests > cache hit, suppressing logs f11a5a11e2584c14 
 @next/third-parties#pack-for-isolated-tests > cache hit, suppressing logs 988b5be9471a943e
```
2026-04-29 12:09:11 +00:00
Tim Neutkens 263dac30bf test: use Turborepo pack task for test isolation (#92575)
### What?

Eliminates the expensive temporary repo directory (`tmpRepoDir`) copy
during isolated test setup by leveraging a Turborepo `pack` task with
caching.

### Why?

The previous test isolation flow copied the entire `packages/` directory
to a temp location, mutated every `package.json` to rewrite workspace
dependency references, and ran `pnpm pack` sequentially for each
package. This added ~10s+ of overhead per test suite. With Turborepo
caching, repeated runs are 500ms (and can still be improved).

### How?

- Adds a `pack-for-isolated-tests` task to `turbo.json` (depends on
`build`, outputs `packed.tgz`)
- Adds a `pack-for-isolated-tests` script (`pnpm pack --out
./packed.tgz`) to every workspace package
- Simplifies `linkPackages` in `repo-setup.js` to scan for pre-built
tarballs instead of copying/rewriting/packing
- Updates `create-next-install.js` to run `pnpm turbo run pack`
(benefits from caching) and use package manager `overrides`
(pnpm/npm/yarn) to resolve transitive workspace deps from local tarballs
- Removes `tmpRepoDir` handling from `base.ts` and `run-tests.js`

**Before:** `createNextInstall` ~16s (copy + sequential pack)
**After:** `createNextInstall` ~5s (first run) / ~4s (cached)

<!-- NEXT_JS_LLM_PR -->
2026-04-13 17:38:33 +02:00
Tim Neutkens 9cf9a63264 test: skip .next and other build artifacts in writeFiles filter (#92578)
### What?

Skip `.next`, `.next-profiles`, and `.DS_Store` directories in the
`writeFiles` filter when copying test fixture directories.

### Why?

`writeInitialFiles` was taking ~500ms because the `fs.cp` filter only
skipped `package.json`. When a test uses `files: __dirname`, local build
artifacts like `.next` (117MB / 411 files) and `.next-profiles` (147MB)
were being copied into the test directory unnecessarily if a dev/build
run had previously happened on the test app.

This really only applies to working on the repository locally, but it's
a nice win for that case.

### How?

Expanded the `fs.cp` filter to also skip `.next`, `.next-profiles`, and
`.DS_Store`. These are local artifacts never tracked in git.
`node_modules` is intentionally not skipped since some tests include
tracked `node_modules` as fixtures.

<!-- NEXT_JS_LLM_PR -->
2026-04-09 17:36:10 +02:00
Sebastian "Sebbie" Silbermann 9aed3b1f0a [test] Ensure incomplete minimum release age isn't propagated to fixture installs (#92568) 2026-04-09 16:37:58 +02:00
Niklas Mischkulnig 7ac71949ab Turbopack: switch from immutable token to path prefix (#92164) 2026-04-08 22:54:08 +02:00
Sebastian "Sebbie" Silbermann bdf8bea015 Update React from 74568e86-20260328 to 404b38c7-20260408 (#92536) 2026-04-08 22:10:09 +02:00
Sebastian "Sebbie" Silbermann 5f25885874 [devtools] Show AggregateError.errors in the error overlay (#91835) 2026-03-27 07:21:19 -07:00
Andrew Clark 138092696c [Segment Bundling] [Scaffolding] Ensure inlining hint correctness (#91320)
Inlining hints (which segments to bundle together) are computed once at
build time by measuring gzip sizes, then persisted to
`prefetch-hints.json`. There are several scenarios where these hints may
not be available at render time. This commit addresses each scenario to
ensure the client always receives correct hints and never enters a bad
state.

### Build-time static pages (stale hints)

The initial RSC payload baked into the HTML is generated before hints
are computed, since hint computation requires at least one completed
render to measure sizes. The server marks these trees with
`InliningHintsStale`. The client immediately expires the route cache
entry so that the next prefetch re-fetches the correct tree from the
`/_tree` response. The segment data doesn't need re-fetching since it
was already cached from the initial HTML payload.

### ISR / revalidation

Hints are always available from the manifest. A missing entry is an
internal error — it means the build pipeline failed to produce hints for
a route that needs them.

### Fully dynamic routes at runtime

No hints exist and none will ever be computed. Every segment gets
`PrefetchDisabled`, which tells the client not to attempt prefetching.
This avoids an infinite re-fetch loop that would occur if the client
kept trying to fetch "correct" hints.

### Unified response format

Also simplifies the segment prefetch response format: every response is
now a `SegmentPrefetchResponse` with a top-level `buildId` and a `data`
array, treating a single-segment response as a bundle that happens to
have length one. This unifies the format so that bundled multi-segment
responses (added in a follow-up) require no additional client parsing
logic.
2026-03-22 22:42:48 -04:00
Sebastian "Sebbie" Silbermann 672b02b270 [next-playwright] Use unique cookie values for instant navigation testing lock (#91250)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-03-16 21:07:56 +00:00
Luke Sandberg 2e4f08ca0a fix(build): don't block SSG on telemetry flush, add persistence spans to trace-build (#91335)
### What?

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

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

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

### Why?

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

### How?

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

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

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

**Test updates**:
- Enabled `turbopackFileSystemCacheForBuild: true` in the trace-build test fixture
- Updated the Turbopack inline snapshot to include `turbopack-build-events`
2026-03-15 23:42:17 -07:00
Will Binns-Smith 5394d39a62 [Turbopack] write route bundle stats to .next/diagnostics/route-bundle-stats.json (#90949)
After a successful, Turbopack production build, this writes
`.next/diagnostics/route-bundle-stats.json`
containing First Load JS stats for every route. 

## Why?

We used to have route bundle size statistics print after every build. It
was not very high-signal and could be misleading, often using heuristics
to give a best-effort estimate of what sizes may be at runtime. **These
kinds of metrics for route bundle sizes can be misleading, as pages can
be slow to visual/interactive completeness even if they have small first
load bytes.**

However, a minimal gauge of how much JavaScript a route ships during
initial page load can be a useful signal so long as users have
additional context and awareness of their app structure. Let's provide
just this number for now for those that could benefit.

Additionally, this does so in the form of writing a json document to the
build directory, rather than writing a table to stdout, where users have
needed to write extra tools to parse.

## Format

An array of route entries sorted descending by
`firstLoadUncompressedJsBytes`:

```json
[
  {
    "route": "/blog/[slug]",
    "firstLoadUncompressedJsBytes": 124928,
    "firstLoadChunkPaths": [
      ".next/static/chunks/framework.js",
      ".next/static/chunks/main.js",
      ".next/static/chunks/pages/blog/[slug].js"
    ]
  }
]
```

`firstLoadUncompressedJsBytes`: sum of uncompressed sizes of all JS
chunks loaded on first navigation to the route (shared chunks included),
for both Pages Router and App Router
`firstLoadChunkPaths`: paths to those chunks relative to the project
root

## Test Plan
Added an integration test. Built with a create-next-app and verified the
json document is written and the numbers align with what Chrome reports
during initial load of a page.
2026-03-13 14:48:51 -07:00