## Summary
When PPR is enabled, requests matching a custom `htmlLimitedBots`
pattern could still use the prerendered route path. As a result, the
configured blocking-metadata behavior was not applied consistently.
Use the existing streaming-metadata decision when selecting the PPR
render path. Built-in bots retain their existing fully buffered
behavior, custom HTML-limited bots can continue streaming body content
after metadata resolves, and regular user agents remain unchanged.
## Verification
- HEADLESS=true pnpm test-start-turbo
test/e2e/app-dir/html-limited-bots-ppr/html-limited-bots-ppr.test.ts
- HEADLESS=true pnpm test-start-webpack
test/e2e/app-dir/html-limited-bots-ppr/html-limited-bots-ppr.test.ts
Co-authored-by: Sebastian Silbermann <[email protected]>
## Summary
Client-handled Server Action redirects currently return a `303` without
a `Location` header and may include a streamed RSC response body. Some
HTTP intermediaries reject this response or strip its body because it
looks like an HTTP redirect.
Return `200` for client-handled Server Action redirects while continuing
to use `x-action-redirect` and the RSC response. Progressive-enhancement
form submissions still return a standards-compliant `303` with
`Location`.
Fixes#92882Fixes#74026
## Verification
- `HEADLESS=true pnpm test-dev-turbo
test/e2e/app-dir/actions/app-action.test.ts
test/e2e/app-dir/app-basepath/index.test.ts
test/e2e/app-dir/actions/app-action-progressive-enhancement.test.ts`
- `HEADLESS=true pnpm test-start-turbo
test/e2e/app-dir/actions/app-action.test.ts
test/e2e/app-dir/app-basepath/index.test.ts
test/e2e/app-dir/actions/app-action-progressive-enhancement.test.ts`
- `pnpm --filter=next types`
<!-- NEXT_JS_LLM -->
## Summary
- Preserve the original static worker request when generating the
Turbopack module-context lookup, instead of passing an already-rewritten
URL object.
- Adds an e2e fixture for bare-module Worker URLs with conditional
wildcard exports.
It looks like this might have regressed in #88602Fixes#94015Closes#94043
## Verification
- `cargo check -p turbopack-ecmascript`
- `HEADLESS=true pnpm test-start-turbo
test/e2e/app-dir/worker-module-url/worker-module-url.test.ts`
- `HEADLESS=true pnpm test-start-webpack
test/e2e/app-dir/worker-module-url/worker-module-url.test.ts`
We're not planning to re-enable this workflow, and instead are handling
the upgrade PRs from within the applications themselves.
This removes them since they are currently failing.
This flag has been enabled by default on canary for some time. This
turns it on by default to ensure that CDNs that don't respect `Vary`
properly distinguish between different response types.
The intel runners were quite a bit slower than the base arm runners. We
were previously compiling both architectures on self-hosted arm so this
just back the status quo on GH runners to improve speed. This shaves off
~10/20 mins.
Validated
[here](https://github.com/vercel/next.js/actions/runs/25071926790/job/73454315459)
Moves all release workflows off of a GH PAT and uses an app with a
short-lived token instead.
Test Plan:
Dry run
[here](https://github.com/vercel/next.js/actions/runs/24934593874).
However, this workflow is blocked until we figure out commit signing for
the bot app. Some options:
- The bot account generates a signing key and we use it in CI (not
great, bypasses the app)
- The org bypasses signature verification for the bot user (also not
great, requires an exemption rule)
- We need to rework the commit step so Lerna does not do the push, and
instead trigger it via the app + GH API. This seems like the best
option, will be added in a follow-up PR.
Note: `create-release-branch` workflow is broken in its current form, as
we will not be restoring administrator privileges to adjust environment
settings. This will become a manual step in the future.
- Add a workflow_run reporter that posts PR test/stats comments using
`GITHUB_TOKEN`.
- Upload PR metadata artifacts from `build-and-test` and `Generate
Stats` so the reporter can safely resolve the target PR
- Move stats/test comment posting out of the external webhook/PAT path
while preserving the existing comment format
This means the test comment won't incrementally update as tests fail,
it'll just post once at the end, but that's a reasonable tradeoff to not
having to worry about a webhook.
## Summary
Switch CI from self-hosted runners to GitHub-hosted runners and fix the
follow-up issues that showed up once the hosted jobs were exercised.
## Changes
- Move Linux workflows onto `ubuntu-latest-16-core-oss`, Windows
workflows onto `windows-latest-8-core-oss`, and the native mac release
lane onto `macos-15-intel`.
- Update the reusable build workflow to work in hosted environments by
relying on `runner.os` instead of label string matching, and by removing
the hardcoded `/home/runner` `fnm` path.
- Make Turbo/sccache configuration explicit for hosted runners:
- unify on `vtest314-next-adapter-e2e-tests`
- pass `TURBO_TOKEN` via workflow env/secrets instead of assuming
runner-level env
- switch Turbo cache mode to `local:rw,remote:rw` so jobs still have
local cache behavior when remote cache is unavailable
- Update the `sccache` action defaults/docs to use the hosted-runner
secret setup (`TURBO_TOKEN`) instead of the old self-hosted token
wiring.
- Make Datadog reporting fail open so missing `DATA_DOG_API_KEY` does
not block CI.
- Fix the CLI reserved-port test so it asserts Next’s own `-p 1`
validation instead of failing earlier on hosted Linux due to
privileged-port binding.
- Replace the live `musl.cc` dependency in `native-builder.Dockerfile`
with musl sysroots imported from GHCR-hosted `rust-musl-cross` images,
keeping the existing `/opt/*-cross` layout used by the Linux native
build scripts.
- Fix the macOS native build workaround s so it applies to all
*-apple-darwin targets based on the Cargo target OS rather than the host
architecture, which avoids napi-build injecting the unsupported -Wl
linker arg when @next/swc is linked with rust-lld on hosted mac runners.
Test Plan:
- This PR's CI
- This
[build_and_deploy](https://github.com/vercel/next.js/actions/runs/24865715983/job/72801225722)
job running on every arch
This updates `_rsc` cache-busting param generation to use a stronger
digest so different RSC request variants are much less likely to produce
the same value.
It also updates the related request-validation and injected
resume/instant-test paths so they continue to generate and validate the
same `_rsc` values across client, server, and edge-reachable codepaths.
Moves the `environment` into `publishRelease` rather than
`trigger_release` since the actual action we want to create environment
rules for is the publish step.
Switches from a long-lived token to trusted publishing OIDC flow. This requires a bump to Node (for feature support). Otherwise just dropping unnecessary envs.
With the new `partialFallbacks` heuristic, routes that weren't part of
`generateStaticParams` will now upgrade their shells to include the
params as if they had been in `generateStaticParams`, but only when the
page with the unknown param was lazily visited.
This tweaks the heuristic to allow segment prefetches to also trigger
the upgrade behavior. This will ensure subsequent clients, when
prefetching the upgraded route, will receive the entry with params
filled in rather than showing the generic fallback.
In a subsequent PR, we'll make it so that the requesting client that
triggers the upgrade path can also benefit from the params being
upgraded, as the client router currently has no awareness that a
background revalidation completed.
Fixes a few things:
- semver wasn't available because deps weren't being installed
- Fixed it to compare latest release with latest canary -- it was
erroneously marking a backport as not a backport.
Test plan:
Dry run results here:
https://github.com/vercel/next.js/actions/runs/24571828615/job/71846911677
```
should_dispatch=true
reason=Dispatching canary preminor release because stable release 16.2.4 is ahead of 16.2.1-canary.46
released_version=16.2.4
current_canary_version=16.2.1-canary.46
dispatch_input=false
auto_dispatch_enabled=true
```
This adds an automated path to keep canary semver-ahead after a
successful stable backport publish.
Today, a backport release can produce a stable version that is newer
than the current canary version, which creates confusing version
ordering for tooling. This change adds a follow-up workflow that
evaluates completed backport publishes and, when appropriate, triggers a
canary preminor release on canary.
The `Sync Canary After Backport Release` workflow evaluates whether a
completed `build-and-deploy` run should trigger a canary sync.
The evaluation only triggers when:
- the upstream `Potentially publish release` job succeeded
- the head commit is a stable release commit
- the published stable version is a backport relative to next@latest
- the current canary version is not already semver-ahead
- there is not already a queued or in-progress Trigger Release run on
canary
When all checks pass, the workflow dispatches trigger_release.yml on
canary with:
- releaseType=canary
- semverType=minor
- force=true
(This is currently gated behind an env flag so we can dry-run it)
This makes `experimental.partialFallbacks` the default when
`cacheComponents` is enabled.
For routes that prerender only a subset of params via
`generateStaticParams`, requests for unknown params used to either
suspend to a nearby fallback or block until the full route finished
rendering. In the cases where the route could already produce a
contentful shell because param access happened below a Suspense
boundary, the old default still served that generic shell on every
request.
With `partialFallbacks` enabled by default, the first request still
receives the best shell available for that route, but Next.js now
revalidates in the background using the newly discovered param values.
That allows subsequent requests to reuse a more specific cached result:
- If the route becomes fully static for those params, later requests get
the fully prerendered page.
- If the leaf remains dynamic, Next.js upgrades the shell as far as it
can, filling in the newly known static params while the dynamic portion
continues to stream.
This also fixes a bug that was caught now that we ran this flag against
the entire CI. A concrete path like `/optional-catchall` could match the
generic `/optional-catchall/[[...slug]]` shell and incorrectly serve
fallback-shell behavior even though that exact path was already
prerendered. This change keeps concrete prerendered paths serving their
concrete result instead of downgrading back to the generic shell.
This PR also removes redundant `partialFallbacks: true` test config and
adds coverage for the explicit opt-out behavior (`partialFallbacks:
false`).
When a route with params specifies `generateStaticParams`, the meaningful outputs are the sub-paths contained within. The top level route just corresponds with the file path, which isn't representative of a static or partially prerendered output. Previously it'd just inherit one of the sub-path values (ie if one path was PPRed, it'd show PPR, even if some were fully static).
This removes the symbol from those types of outputs, since #92518 moved those symbols to be within the subpaths.
For truly dynamic param cases (ie, no subpaths), the rendering symbol will still appear.
With `cacheComponents`, it's possible with `generateStaticParams` to produce a fully static route, while other sub-routes might still be partially prerendered. This updates the build info to distinguish between the two.
Before:
```
├ ◐ /params/semantics/[lowcard]/[highcard]/layout-has/server
│ ├ /params/semantics/[lowcard]/[highcard]/layout-has/server
│ ├ /params/semantics/one/[highcard]/layout-has/server
│ └ /params/semantics/one/build/layout-has/server
```
After:
```
├ ◐ /params/semantics/[lowcard]/[highcard]/layout-has/server
│ ├ ◐ /params/semantics/[lowcard]/[highcard]/layout-has/server
│ ├ ◐ /params/semantics/one/[highcard]/layout-has/server
│ └ ○ /params/semantics/one/build/layout-has/server
```
These tests won't ever be run in a deployed environment, so this removes
`skipDeployment` so it's easier to grok which tests are intentionally
disabled because they don't work when deployed.
We don't need to deploy a single image component example every time
`build_and_deploy` runs. If something about this example changes, it can
be manually re-deployed.
We recently re-imaged the self-hosted Linux runners and it now hits
`ERR_PNPM_EXDEV` when pnpm copies packages between its default store and
the temp stats workspace. Keeping both under the same temp root avoids
the cross-filesystem copy failure.
When a `notFound()`, `forbidden()`, or `unauthorized()` error escapes
into the outer prerender recovery path, we were falling back to the
generic error shell flow.
In the `cacheComponents` case, that could leave us with:
- error HTML rendered from `ErrorApp`
- Flight data reused from the aborted prerender prelude
- references to Flight chunks that were never emitted
That is what caused the client-side `Connection closed` failure in
#86251.
Instead of rerendering the full Flight tree or always using the generic
error RSC payload, this change:
- finds the deepest matching HTTP fallback boundary
- rerenders the normal app router payload with that segment-scoped
fallback
- tees the replacement Flight stream so Fizz can render from one copy
while prerender buffering consumes the other
- only takes this path for recoverable HTTP access fallbacks that have a
real boundary (ie a defined not-found/unauthorized/etc)
If no matching boundary exists, we keep the existing generic error
handling.
Fixes#86251Fixes#90837Closes#87041Closes#86251
Closes NAR-711
Closes NEXT-4876
Previously, `partialFallback` was only emitted when all dynamic params
could become fully concrete after upgrade. For a route like
`/prefix/[one]/[two]` where only `[one]` has generateStaticParams, the
feature was disabled entirely.
This relaxes the guard to allow adapters to produce an intermediate
shell for not fully known params, and tweaks the handling to support
when both known and unknown params are provided to the render.
Concretely:
- When partial `nxtP*` params are provided during background
revalidation but `normalizeDynamicRouteParams `rejects them (missing
params), merge the provided params into the existing params to override
placeholders.
- Compute `effectiveFallbackRouteParams` by filtering to only params
that still have placeholder values. Use this filtered set for
`fallbackRouteParams` so intermediate shells render resolved params in
the static shell and only suspend for truly unknown params
The regex associated with this flag was the same value in both the
true/false case and the underlying flag was only used to control this
regex. This appears to be dead code, so removing it.
In a deployed environment, `onCacheEntryV2` determines whether a
postponed response is HTML-origin or RSC-origin by checking whether
meta.url ends with `.rsc`.
For some dynamic full-route RSC requests, Next was passing a URL with
query params into onCacheEntry, for example:
`/[tenant]/samples.rsc?nxtPtenant=tenant-x`
Because that string does not end with .rsc, `onCacheEntryV2`
misclassified the request as HTML and treated it as text/html instead of
an RSC response.
This PR normalizes the URL before passing it to `onCacheEntry`, so the
callback receives the pathname rather than the full URL-with-query.
---------
Co-authored-by: JJ Kasper <[email protected]>
This improves the blocked-request warning so it names the actual dev
resource being requested and gives clearer guidance on how to allow it.
When the source host is known, the message includes an inline
`allowedDevOrigins` config snippet; when the source is missing or
opaque, it explains why Next.js cannot infer a host to allow.
This removes the warn-only default behavior and enforces the dev-origin guard by default. Cross-origin requests to internal dev resources now block unless they match the built-in local allowlist or an explicit `allowedDevOrigins` entry. The tests are expanded to cover default blocking, configured-but-not-allowlisted hosts, missing Referer in the no-cors path, and same-site requests without an Origin, and the docs are updated to match the new behavior.
This PR makes configured `allowedDevOrigins` apply to cross-site no-cors dev asset requests. When browsers omit Origin for subresource loads, the dev guard now falls back to `Referer` so explicit allowlisted hosts can load `/_next/*` resources in development.
Previously, when `allowedDevOrigins` was configured, cross-site no-cors requests to internal Next.js dev resources were still blocked even for allowlisted hosts, because that code path never consulted the allowlist.
These tests have started failing in deploy mode in environments outside
of the new Vercel adapter. Shell related behavior is changing as part of
the new `partialFallbacks` flag and will not be backported to the old
CLI, so this gates the test to ensure it runs in the right deployment
environment.
In #91158, I wired up the plumbing to indicate whether a partial
fallback shell is upgradeable. The previous heuristic was too coarse.
This wires up the handling in `next start`.
This PR teaches the runtime to complete a fallback shell only as far as
prerendering allows. Instead of promoting a generic shell like
`/[one]/[two]` directly to `/c/foo`, it promotes it to the most specific
prerendered shell for that branch, `/c/[two]`, where `[one]` has
`generateStaticParams`. That means later requests can reuse the more
complete shell, while fully dynamic params continue to stream as dynamic
content.
With this change:
- the first request still serves the generic fallback shell immediately
- the background revalidate writes a more specific shell, not a fully
concrete route
- later requests for the same prerendered branch reuse that shell
- upgrading stops once only fully dynamic params remain
This keeps the runtime behavior aligned with the build-time
`remainingPrerenderableParams` contract from the downstack PR.
Partial fallback upgrading is currently too broad. Any PPR fallback shell can be treated as upgradeable, including routes with no `generateStaticParams` and shells that are already as specific as prerendering can make them.
In practice, that means we preserve `allowQuery` and enable background promotion for shells that should remain as partial fallbacks. That broadens the feature beyond the medium-cardinality case it was meant for and makes the build output imply per-param upgradeability when there are no prerenderable params left to fill.
This PR teaches static-path generation to compute `remainingPrerenderableParams` for each fallback shell: the unresolved pathname params that can still be filled by `generateStaticParams` in that branch. We then use that signal to gate partial fallback behavior.
With this change:
- `partialFallback` is only emitted for shells that still have prerenderable params remaining
- `allowQuery` with params is only preserved for those shells
Effectively, this limits partial fallback upgrading to shells that can still be made more specific by prerendering.
## Summary
Fix an App Router request normalization gap where app-page route-module
requests could keep internal RSC/segment-prefetch URLs in `req.url`.
This could leak paths like:
- `.../.segments/_tree.segment.rsc`
- `.../__PAGE__.segment.rsc`
into App Router render state and surface through `usePathname()` during
server rendering.
## What changed
- Normalize `req.url` in the app-page route-module path after `.rsc` and
`.segments/*.segment.rsc` pathname normalization.
## Why
`base-server` already keeps `req.url` in sync after RSC normalization
([ref](https://github.com/vercel/next.js/blob/5e11e67759fa38fe67f70790d7bba8c9ff9ba5fd/packages/next/src/server/base-server.ts#L676-L680)),
but the app-page route-module path only updated `parsedUrl.pathname`.
Since App Router render state seeds its canonical URL from `req.url`,
that desync could cause internal segment-prefetch paths to be treated as
the current pathname.
This puts the partial fallback upgrading feature introduced in
https://github.com/vercel/next.js/pull/89063 behind a flag to make it
easier to test the rollout/eventual stabilization in isolation.
In https://github.com/vercel/next.js/pull/69282, we landed support for
Partial Fallback Prerendering. The intention behind this feature was to
be able to upgrade from a partial fallback (essentially the UI produced
when `await params` is dynamic, ie the param was not part of
`generateStaticParams` at build time) to a route shell (when the param
values are filled in, and thus we can prerender more of the page).
It was reverted in https://github.com/vercel/next.js/pull/79258 because
we didn't add first-class support to this in the build output API and
the necessary infrastructure changes to perform similar behavior that
this PR does to trigger a background revalidation with params filled in.
This re-lands the feature in Next.js now that upstream support landed
(https://github.com/vercel/vercel/pull/14703,
https://github.com/vercel/vercel/pull/15338, and Vercel infra changes
which are rolling out)
Analogous change to https://github.com/vercel/vercel/pull/15338, but for the new Next.js adapter API.
This updates only allowQuery for fallback HTML when the route is a true partialFallback (PPR fallback with postponed state) and cache components is enabled. For all other fallback cases, behavior stays the same as today (allowQuery is cleared to [] for shared fallback caching).
This change is needed because partial-fallback shells can be param-specific, so collapsing them to one shared fallback cache key can serve the wrong shell across params and block correct upgrade behavior. Gating this behind partialFallback lets us roll out the new behavior safely and independently, without changing legacy fallback semantics for non-partial paths.
Plumbs the new `partialFallback` through `onBuildComplete`, which is
consumed by `adapter-vercel` to signal that a prerender is a partial
fallback (PR forthcoming, once this lands, so it's available on the
types). For more information on what a partial fallback is, see the
downstack PR.
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:
## For Contributors
### Improving Documentation
- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide
### Fixing a bug
- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
### Adding a feature
- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md
## For Maintainers
- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change
### What?
### Why?
### How?
Closes NEXT-
Fixes #
-->
Adds a failing test proving that if the RSC response from a prefetch
contains an unresolved promise, navigating to the page will cause the
Suspense boundary wrapping the use of that promise to remain in fallback
state indefinitely.
This appears to have regressed in
e9a03ac12c, though the theory is that this
is just uncovering a React bug rather than causing the issue.
Closes#89170
`NextURL` intentionally canonicalizes loopback hosts (127.x.x.x, [::1],
localhost) to localhost so that internal URL handling is consistent
across equivalent local origins.
However, we were applying a regex replacement to the entire URL before
parsing, which unintentionally rewrote loopback strings inside query
parameter values, eg `?redirect_uri=http%3A%2F%2F127.0.0.1...`. This is
unexpected for anyone that is explicitly passing through different
loopback values.
This fixes it by first parsing the URL with `new URL()`, and then only
apply the replacement logic to `parsed.hostname` when it is a loopback
host. This leaves search unchanged.
Fixes NEXT-4864
`maxPostponedStateSize` should be verified in all cases where the
postponed body is buffered. This fixes a branch in self-hosted setups
where that check was missing.
This fixes an issue where deploying an app that explicitly named a
catch-all segments as `[...segments]` or `[[...segments]]` would lead to
repeated ISR cache misses on that route, even if it was generated at
build time.
This is because the normalizer that is responsible for extracting the
segment path wasn't escaping the `.segments` directory value, so the `.`
was treated as a wildcard. That meant when the server attempted to look
up the underlying segment data, it wouldn't find it and would have
responded with a 204, which signals to the upstream CDN that it is not a
cacheable response.
Fixes#87090
This restores fast navigations to fully static PPR routes in Cache
Components, even when prefetch hasn’t completed or `prefetch={false}` is
used. In legacy versions of PPR, the `prefetchDataRoute` entries would
be populated with `.prefetch.rsc` values containing static RSC payloads.
This regressed in cache components, and the only static RSC payloads
were in the `.segments` directory. However, when Next.js triggers a
regular RSC request (non-prefetch), either because the prefetch hadn't
completed yet or `prefetch={false}` was configured, we'd route to the
empty fallback and trigger a function invocation.
This restores old behavior of emitting a `.rsc` file for a fully static
route. This information technically also exists in the `.segments`
directory, but it's split into parts, and it's now routed to unless
explicitly done so via the `next-router-segment-prefetch` header. In
addition to emitting the route, this also ensures we set
`prefetchDataRoute` to point to this. This is how the Vercel adapter
knows the file to serve.
An e2e regression test now asserts that `prefetch={false}` navigation
does not wait for a dynamic render. The deploy test will be unblocked
once https://github.com/vercel/vercel/pull/14770 lands.
We originally prefixed the TTL sentinel with the null-byte separator to
avoid collisions with real invocation IDs, but that makes
`extractInvocationID` return "ttl" instead of undefined for TTL-mode
keys.
This switches to a reserved magic string sentinel so TTL-mode entries
are correctly treated as “no invocation ID”.
When generating PPR fallback shells, a cache miss during warmup was
being treated as a dynamic hole, so the warmup render never filled new
cache entries. This caused root‑param fallback shells (e.g.
`/en/blog/[slug]`) to suspend even though root params were already
known.
This change always creates a fresh prerender RDC for warmup and copies
any seed hits into it. The final render then uses the warmed cache, so
fallback shells can reuse populated entries without short‑circuiting. On
the export side, we now choose a seed RDC by matching known params (e.g.
locale) so /en/... fallback shells don’t get seeded with /fr/... data.
(Previously, the RDC seed was "last-write-wins".
Fixes NAR-716
When digging into failing deployment tests, I often will re-run the test
itself without wanting to change anything about Next.js or the test
application, especially when I suspect a logic bug in the test itself or
need to figure out what's causing a flake.
This adds an environment variable that can be used to skip the
build/deploy step and will use an existing deployment URL to run the
tests against.
I suggest reviewing this [without
whitespace](https://github.com/vercel/next.js/pull/88829/changes?w=1).
When a server action triggers a re-render of the underlying page (eg by
calling `refresh()`), cached data that was part of the Resume Data Cache
(RDC) was not provided to the server action handler, and so those parts
of the UI would update with new data. A subsequent refresh of the page
would revert back to the ISR data, which means the server action causes
the data to temporarily get into an "ahead" state that will never
actually be reflected in the ISR shell.
This provides the RDC data to the server action handler. For self-hosted
deployments, Next.js can look up the cached entry directly since the
cache and renderer are co-located. For deployments on Vercel, the
caching layer and rendering layer are separate services, and server
actions bypass the cache. This means the serverless function has no way
to access the cached RDC on its own, so the underlying CDN must fetch it
and pass it via the request body. The `x-next-resume-state-length` is
sent to the serverless function so that it can determine how to split
off the RDC data from the action body.
Fixes NAR-478
Fixes NAR-502
When self-hosting Next.js, it's possible to customize a cache handler
for ISR. Self-hosted Next.js also makes use of a similar cache interface
for `next/image`, but there's no way to customize this cache handler.
This plugs into the [existing
ability](https://nextjs.org/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath)
to customize a `cacheHandler` to support the `IMAGE` type. This lets you
implement whatever constraints you might have on your self-hosted
environment, eg the need for an LRU cache, or storing optimized images
outside of disk.
This is currently opt-in because it's a breaking change for cache
handlers to start receiving `IMAGE` cache types, which we'll make
default in the next major.
This was caught by our React 18 tests which only run on `canary`. There
was a console error about breaking `javascript:` urls in an upcoming
React release but it didn't actually block them.
Using `taint` APIs from React without enabling `experimental.taint` will
error at runtime because it needs to bundle experimental React, making
it hard to catch and potentially leading to downtime. This updates our
transform to throw an error if we detect that it's imported but `taint`
isn't enabled.
Fixes NAR-690
When we landed #76207 we started propagating the
`stale-while-revalidate` value to RSC responses. We wanted a way to
express to CDNs that it can keep serving the cached content for some
time while a revalidation runs in the background, but we specifically
don't want that for private caches.
As a result of this header, when re-building your application locally
(or doing something on the server that would have expired the cache),
the browser's disk cache would serve a stale RSC response. We currently
encode a buildId in RSC payloads, so the client router will discard RSC
responses that don't match what it is expecting.
Unfortunately there isn't a `s-stale-while-revalidate` header to express
the intended semantics. This moves the `stale-while-revalidate`
configuration into the `CDN-Cache-Control` header, which is becoming
standardized and is supported by a wide variety of CDNs. This allows us
to specify the SWR behavior more explicitly to CDNs while not impacting
private caches like the browser. It restores the previous behavior of
only setting `s-maxage` on the `cache-control` itself to avoid
influencing browser cache.
If Next.js is deployed to a CDN that doesn't support this header, it can
be customized via `nextConfig.experimental.cdnCacheControlHeader`. Eg,
if deployed to Fastly, you'd configure this to `cdnCacheControlHeader:
'Surrogate-Control'`.
Closes NAR-525
Reference docs:
[Vercel](https://vercel.com/docs/headers/cache-control-headers#cdn-cache-control-header)
[Cloudflare](https://developers.cloudflare.com/cache/concepts/cdn-cache-control/)
[Fastly](https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Control/)
[RFC 9213](https://httpwg.org/specs/rfc9213.html)
These tests aren't resilient to changes in router behavior after
extensive refactors. What looks to be happening is a race between the
prefetch being issued and the click firing:
1. Cache expires -> refetch prefetch is triggered
2. Click happens while prefetch is in-flight -> route entry is Pending
3. Since no fulfilled cache entry exists, `navigate()` falls through to
`navigateDynamicallyWithNoPrefetch()`
4. This issues a full dynamic request
5. The full dynamic response is favored and skips the loading state
This ensures that we are waiting for network activity to finish before
navigating.
Fixes a few failing deployment tests that weren't validated when landed
last week.
- next.cliOutput is not available when deployed because it's part of
runtime logs
- status code changes on Vercel because POSTing to a static 404 page is
treated differently.
When an error is thrown inside a Suspense boundary during streaming SSR,
the error was not being captured in the "render route (app)" span. This
happened because `getTracer().wrap()` ends the span as soon as the
stream is created and returned, but errors inside Suspense boundaries
occur asynchronously during stream consumption.
This PR changes the span management from using `wrap()` to manually
calling `startSpan()` and tracking the span lifecycle. The span now
stays open until `allReady` resolves or rejects. A new `withSpan()`
helper was added to the tracer to properly activate the span context so
child spans are parented. Error handlers now accept an optional
`spanToRecordOn` parameter to record exceptions on the correct span
rather than relying solely on the active scope span.
Fixes#86517
The LRU and cache map code was using any types in several places
`(MapEntry<any>`,` type LRUNode = MapEntry<any>`) which meant we had no
type safety for those operations. This masked a bug fixed in #87124
where `deleteFromCacheMap(tail.value)` was being called incorrectly -
the `any` typing let it compile when it shouldn't have.
This PR introduces a `LRUNode` type that captures the structure needed
for heterogeneous LRU operations (since the LRU holds both
`RouteCacheEntry` and `SegmentCacheEntry`). The generic `MapEntry<V>` is
kept for type-safe map operations, while `LRUNode` is used for deletion
and LRU logic.
- Removes support for urlencoded actions
- Errors when a server action request is received in apps that have no
server actions
- validate action ID in MPA actions before proceeding
---------
Co-authored-by: Josh Story <[email protected]>
When navigating away from a page due to an MPA navigation or browser
refresh, we were aborting any pending RSC requests. However, in the case
where the abort was due to an MPA navigation, the browser would restore
the execution context via the built-in bfcache, and the aborted fetch
would cause React to get in an errored state, leading to
"BodyStreamBuffer was aborted".
Rather than aborting the pending requests, we instead mark that we would
have aborted due to the page unloading. That still solves the original
reason the flag was added (suppressing the fetch errors that get logged)
Fixes NAR-519
When configuring `cacheMaxMemorySize` to disable memory caching
(typically used for setups to adjust production caching behavior,
especially in multi-process self-hosted setups), this inadvertently also
disabled dev HMR caches which are intended as a performance
optimization. As a result of this, the dev console would be spammed with
"Single item size exceeds maxsize".
It's still possible to use this configuration to set a larger cache, but
otherwise it will have a minimum set to the previous default.
Fixes NAR-463
x-ref:
https://github.com/vercel/next.js/pull/85153#issuecomment-3429636751
The Vercel CLI produces a lot of `/_next/data` routes regardless of if
pages router is in-use. There's a lot of different metadata we emit but
nothing concretely tells us if an app has pages router enabled. For app
router, we rely on the presence of `app-paths-manifest`, but in pages
router, we always emit a `pages-manifest`.
Rather than trying to cobble together something in the CLI, this updates
the routes manifest to explicitly output the app type (pages, app, or
hybrid).
When we fixed deploy tests to consistently run with the explicitly
provided Next.js release version, it broke this job which was relying on
an explicit match on the "test e2e deploy canary" string.
It's safer to copy here than to pass a reference, because it's possible
that the underlying chunk became detached. Accidentally tweaked this
before landing #85595
Next.js relies on on the Resume Data Cache to prevent tearing between
the statically prefetchable parts of the UI by seeding the resume with
the same cache as the revalidation/build. The RDC data is stashed in the
postponed state which is attached to all of the different outputs that
comprise a page (the segments, the HTML, and the dynamic RSC data).
However, when deployed to targets like Vercel, the revalidation was
incorrectly bypassing the cache for the RSC entry, because it thought
that it was a resume. This led to a drift in cached values, because the
revalidation would cause the main RSC entries RDC data to contain a
different value than what was used for the segment prefetches & HTML ISR
entry.
Fixes NAR-493
Co-authored-by: JJ Kasper <[email protected]>
Runtime prefetching relies on the presence of headers to determine
caching behavior: `x-nextjs-postponed` indicates if the response was
partial (so it knows if it should trigger a dynamic request to retrieve
the missing data), and `x-nextjs-stale-time` provided stale time
information to the client router. This worked fine in `next start` but
due to how this is modeled in our Vercel deployment adapter, it was not
possible to set runtime headers after the initial headers have been
sent, as these requests were routed to prerenders.
As a result of this limitation, this moves both the isPartial flag and
the stale time value into the payload body. The response is run through
a transform stream that replaces a sentinel value (`"rp": [sentinel]`)
with the final stale time/isPartial values after we determine them. This
avoids regenerating the entire RSC payload while ensuring correct values
are sent to the client. Both properties are only included in runtime
prefetch responses.
Closes NAR-494
---------
Co-authored-by: Josh Story <[email protected]>
This property is from back when we forked client router handling based
off the presence of the `postponed` payload, but it's no longer being
read by anything, so this PR removes it.
Moves `resume-data-cache` to `test/e2e`. We don't currently run
`production` tests when deployed - many of them do funky things with
`next.build` / `next.start` and are meant to test local behavior rather
than deployed behavior.
As a result, I also temporarily disabled this test when deployed because
there are currently bugs.
This removes the indirection that was set up to codesplit on the client.
But now that there's only a single client cache entrypoint, we can
delete the indirection and move everything to a top-level export.
Turning this flag off would have already caused client-side invariants
because we deleted all the old codepaths. This fully removes the
remaining plumbing that supported the flag.
When a server action triggers a redirect, it will return the RSC payload
for the destination state in a single roundtrip. This lets us update the
UI without needing to waterfall when getting the target page's data. We
reject the server action with a special error signaling that the form
state should be reset, but otherwise the redirect itself is unnecessary,
since we already are handling it in the server action reducer.
However, when we introduced Activity for bfCache, this meant navigating
back to the previous page would replay the redirect error that was
stashed in the `RedirectBoundary`. I incorrectly patched this by just
returning the current router state, rather than the redirect
destination's state, but this lost the ability to return the updated
state in the same roundtrip as the redirect. Instead, this marks the
error that gets stashed in the `RedirectBoundary` as being "handled".
Re-activating the hidden subtree will now still correctly reset the
state (because the boundary handles it), but won't attempt to perform
the redirect again.
Closes NAR-470
When the tree response sends back a static staleTime, we had handling to
ensure that it was at minimum 30s, because anything less would mean we'd
be discarding static cache entries more frequently than necessary.
Currently a value of 0 would result in a prefetch ping loop, because the
cache value would be immediately invalidated.
This updates our config validation to warn about values lower than 30s
and ensures the `getStaleTimeMs` function applies to the value read from
env, too.
There are a lot of things in here that aren't quite fully fleshed out
yet (eg runtime prefetching) as well as some inaccuracies (eg `use
cache: private` is not required for runtime prefetching, it's just
another thing that _can_ be runtime prefetched).
This trims the doc down until we can focus on proper guidance.
`prefetch={true}` with `cacheComponents` was opting into runtime
prefetching, with `unstable_forceStale` preserving the old behavior of
fetching the full (stale) data. However we only intended to expose
runtime prefetching via the `export const prefetch` segment config.
This removes `unstable_forceStale` and restores previous
`prefetch={true}` behavior. I've disabled some of the tests that were
relying on the old link behavior for opting into runtime prefetching -
we'll need to refactor those to use the segment opt-in or just remove
them all together.
Fixes#85162
Closes NAR-320
Previously we were only showing this during navigations when caches are
bypassed. We want to make it clearer that you are in this state and dev
might not work as intended while caches are disabled.
This will show the cache bypass status on initial load and will reset
the next time a router action occurs that doesn't bypass cache.
https://github.com/user-attachments/assets/f82a98c6-b5be-44d0-ba86-e4f7ca7010e4
Fixes an issue that reproduces due to a combination of the log
forwarding logic alongside logging out an explicit element. We can add
this back in the future once our log forwarding library properly ignores
non-enumerable properties.
This removes the `routerBfCache` and enables it automatically when
`cacheComponents` is enabled. This leverages `React.Activity` on layout
segments to support restoring previously visited UI segments while
preserving state.