Commit Graph

29 Commits

Author SHA1 Message Date
Zack Tanner 4be2ee7443 Preserve per-segment prefetching after dynamic navigation (#96583)
## Summary

Cache Components routes support per-segment prefetching, but dynamic
Flight responses only advertised this capability during static
generation. This could cause a subsequent `router.prefetch()` call to
fall back to loading-boundary prefetching and issue an unnecessary
request.

In other words, this logic was correctly determined
[here](https://github.com/vercel/next.js/blob/0db9e5f994ddf144bef72640d5be9bb17d77fe30/packages/next/src/server/app-render/app-render.tsx#L2204)
but not in the initial payload.

## Verification

- `pnpm test-start-turbo
test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts`
- `pnpm test-start-webpack
test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts`
2026-08-04 02:02:32 +00:00
Hendrik Liebau 2ba73b7a30 Fix per-segment prefetching for initial loads with Cache Components (#90610)
The `S` field in the RSC payload was only set to `true` during static
generation, but the client used it to determine whether a route supports
per-segment prefetching (`isPPREnabled` on the route cache entry). For
partially static pages with Cache Components enabled, the initial HTML
is served via PPR resume, which generates the RSC payload at request
time with `isStaticGeneration` set to `false`. This caused `S` to be
`false` even though the route fully supports per-segment prefetching.

This caused the prefetch scheduler to fall back to the `LoadingBoundary`
fetch strategy instead of `PPR`, which skipped fetching
`_head.segment.rsc` and instead sent a `metadata-only` dynamic request.
That request returned a 404 locally (harmless) but a 200 with an empty
body on Vercel, which interfered with same-page navigation refreshes
introduced in #76223 and exposed by the route cache keying fix in
#90400.

To fix this, `S` is now also set to `true` when `cacheComponents` is
enabled, since all Cache Components routes support per-segment
prefetching. When Cache Components is disabled, `S` continues to rely
solely on `isStaticGeneration`, which is correct because fully static
pages always have both their RSC payload and per-segment prefetch
responses generated at build time.

This PR also renames `isPPREnabled` to `supportsPerSegmentPrefetching`
on the client and updates the `S` field comments in the RSC payload
types to better reflect the field's actual meaning.
2026-02-26 20:21:17 +00:00
Sebastian "Sebbie" Silbermann 1cf02b2c6f [test] Skip failing segment-cache deploy test (#90601)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-02-26 18:48:51 +01:00
Sebastian "Sebbie" Silbermann a77fd58c96 Upgrade React from bef88f7c-20260116 to 41b3e9a6-20260119 (#88756)
Co-authored-by: nextjs-bot <it+nextjs-bot@vercel.com>
2026-01-19 21:44:49 +01:00
Hendrik Liebau a6b2d2e81b [test] Add a failing test for cycle serialization in segment prefetches (#88287) 2026-01-08 21:06:55 +01:00
Zack Tanner 85c3d20a91 [cache components]: move flag out of experimental (#85035)
This moves `experimental.cacheComponents` to a top level config. As part
of this, I disabled some tests in `build-output-prerender` that assert
on `cacheComponents` appearing in the experimental list. In a separate
PR, I'm going to show that Cache Components is enabled next to the
bundler info.

This also updates some docs pages to remove "experimental" language.
2025-10-18 14:13:44 -07:00
Josh Story 8cab15c0c9 [Cache Components] Remove unstable prefix from unstable_cacheLife (#84877)
`cacheLife` is now stable and does not require an unstable prefix
2025-10-15 08:14:04 -07:00
Zack Tanner 6d59d11e35 remove clientParamParsing configuration & bundle it with cacheComponents (#84763)
We can't optionally land this flag in `cacheComponents` so this ensures
it's enabled when using that flag. We still keep around
`rsc.clientParamParsing` as the Vercel CLI still reads from it to opt
into some additional routing configuration, which can be removed in the
future.
2025-10-14 13:08:27 -07:00
Wyatt Johnson 3630146458 Add validation for missing default.js in parallel routes (#84702)
### What?

Adds build-time validation to require explicit `default.js` files for
all parallel route slots (except the implicit "children" slot). This
validation is implemented in both Webpack and Turbopack bundlers.

### Why?

Parallel routes without `default.js` files currently cause silent 404
errors when users navigate to those routes. This creates confusion and
hard-to-debug issues because the routes appear to be configured
correctly but fail at runtime without any indication of what went wrong.

By making this validation explicit at build time, developers get
immediate feedback about missing required files with clear error
messages and documentation links, catching configuration mistakes before
deployment.

### How?

**Rust/Turbopack** (`crates/next-core/src/app_structure.rs`): Added
`MissingDefaultParallelRouteIssue` that emits a build error when a
parallel route slot is missing its `default.js` file. The validation is
skipped for the "children" slot since it's implicit and doesn't require
a default file.

**Webpack**
(`packages/next/src/build/webpack/loaders/next-app-loader/index.ts`):
Added validation that throws `MissingDefaultParallelRouteError` when
`default.js` cannot be resolved. The "children" slot falls back to the
existing `PARALLEL_ROUTE_DEFAULT_PATH` behavior for backward
compatibility.

**Error Class**
(`packages/next/src/shared/lib/errors/missing-default-parallel-route-error.ts`):
New error type with helpful messaging that includes the slot path,
explanation of the requirement, and a link to documentation.

**Migration Path**: Users who want the previous 404 behavior can
explicitly create a `default.js` that calls `notFound()`, or return
`null` for empty slots:

```tsx
import { notFound } from 'next/navigation'

export default function Default() {
  notFound()
}
```

Users can also run the following Deno script to generate the default
files for them:
https://gist.github.com/wyattjoh/ba7263ecb637ef399d3e3e4db63ffbd6

**Breaking Change**: This is a breaking change timed for Next.js 16
beta. Builds will now fail if parallel route slots are missing required
`default.js` files.
2025-10-09 15:20:52 -06:00
Zack Tanner dd5e3ad611 [ci]: move router-act to top level testing utils (#84653)
We're starting to use this pattern in more places, so this moves `router-act` into our top level testing utils.
2025-10-08 17:01:40 -07:00
Zack Tanner ab758f1636 Revert "auto-enable clientParamParsing and clientSegmentCache w/ cacheComponents" (#84419)
Reverts vercel/next.js#84250

Need to investigate deployment failures

[x-ref](https://github.com/vercel/next.js/actions/runs/18154875270)
2025-10-01 11:52:39 -07:00
Zack Tanner efa1ad49e8 auto-enable clientParamParsing and clientSegmentCache w/ cacheComponents (#84250)
When you try `experimental.cacheComponents`, we should auto-enable the
supporting flags (`clientSegmentCache`, `clientParamParsing`), just like
we were doing with PPR to avoid the need to think about all the
different flags needed to get the best cache components testing
experience. Like before, these flags can be explicitly disabled for any
reason.
2025-09-30 09:59:50 -07:00
Zack Tanner f63290fef9 [Segment Cache]: fix infinite prefetching when staleTime is 0 (#82388)
This PR fixes an infinite loop bug that occurred when using `cacheLife({
stale: 0 })` with `clientSegmentCache`. When the server returned a stale
time of 0 seconds, cache entries would have `staleAt = Date.now()`,
making them immediately stale. The cache would evict these entries on
the same tick they were created, triggering continuous refetch requests.

The fix updates the minimum stale time to be 30s, in the event that the
server sends a low value. The rationale being that a value lower than
30s here would render prefetching ineffective. This prevents the
immediate eviction while still respecting the server's intent for very
short-lived cache entries.

Closes NAR-269
2025-08-06 16:11:55 -07:00
Wyatt Johnson 8abbb3dc2e refactor: rename experimental.dynamicIO to experimental.cacheComponents (#81562)
## What?

Rename `experimental.dynamicIO` to `experimental.cacheComponents` across
the Next.js codebase.

## Why?

We're going to be merging the functionality of the `ppr`, `dynamicIO`
and `useCache` experimental flags into the singular `cacheComponents`
flag to reduce complexity of the codebase and simplify adoption for
users wanting to experiment with experimental features.

## How?

- Renamed the configuration option from `experimental.dynamicIO` to
`experimental.cacheComponents`
- Added deprecation handling with automatic migration for the old option
name
- Updated all documentation, tests, and internal references
- Updated Rust code in SWC transforms and Turbopack
- Maintained backward compatibility with deprecation warnings

NAR-158
2025-07-17 19:30:28 -06:00
Hendrik Liebau e186b697fb [Segment Cache] Enable deploy tests (#80947)
With this PR, we're enabling deployment testing for the existing Segment
Cache test suites (excluding those that need a custom server setup).

Two timing issues were fixed, and one bug was uncovered. For details see
the inline comments below.
2025-06-27 09:41:51 +02:00
Hendrik Liebau 2682d81742 Allow intercepting dynamic routes to be partially prerendered (#80851)
For intercepting dynamic routes we do prerender `.prefetch.rsc`, as well
as `.segment.rsc` files if `clientSegmentCache` is enabled. However, we
omitted adding those routes to the prerender manifest, which resulted in
server errors when looking up the RSC files.

I've added two tests to verify the one-line fix, one in
`test/e2e/app-dir/parallel-routes-and-interception` (relevant when tests
are run with `__NEXT_EXPERIMENTAL_PPR=true`), and one in
`test/e2e/app-dir/segment-cache/basic` (has `dynamicIO` and
`clientSegmentCache` enabled). Both tests are written in the style of
the existing tests in the respective test suites.

---------

Co-authored-by: Jimmy Lai <laijimmy0@gmail.com>
2025-06-25 12:26:51 +02:00
Hendrik Liebau 8751df4ee3 [Segment Cache] Fix: Ensure server references can be prerendered (#79448) 2025-05-21 14:40:02 +02:00
Hendrik Liebau 2a95766744 Remove obsolete ppr configs from Dynamic IO tests (#79305)
Now that `ppr` is enabled by default when `dynamicIO` is enabled, we can remove the obsolete `ppr: true` configs from the Dynamic IO tests.
2025-05-19 16:52:03 +02:00
Janka Uryga 4282bc6f91 remove BrowserInterface (#78308)
rip. you served a purpose once, but now you're just getting in the way. this improves typesafety quite a bit, because `BrowserInterface` had a whole bunch of random `any`s everywhere

also
- removes `evalAsync`. no idea why that was needed, but we're happily using promises in normal eval, so it can be dropped
- adds more safety to `chain`
2025-04-18 11:16:15 -07:00
Andrew Clark bcb94e3222 [Segment Cache] Refresh on same-page navigation (#76223)
It's a common UI pattern for apps to refresh when you click a link to
the current page. So when this happens, we refresh the dynamic data in
the page segments.

Note that this does not apply if the any part of the hash or search
query has changed. This might feel a bit weird but it makes more sense
when you consider that the way to trigger this behavior is to click the
same link multiple times.

We should probably refresh the *entire* route when this case occurs, not
just the page segments. Essentially treating it the same as a refresh()
triggered by an action, which is the more explicit way of modeling the
UI pattern described above.

Also note that this only refreshes the dynamic data, not static/cached
data. If the page segment is fully static and prefetched, the request is
skipped. (This is also how refresh() works.)
2025-02-28 12:50:11 -05:00
Andrew Clark 52281196b8 [Segment Cache] Cancel prefetch on viewport exit (#74671)
We use an IntersectionObserver to prefetch links when they enter the
viewport. This updates the behavior to cancel the prefetch if the link
exits the viewport before it completes.

This can greatly reduce the amount of data transfer caused by
prefetching, however, the impact of this change will depend on the
user's network conditions. The faster the network conditions, the more
likely the link will have already been prefetched by the time the link
exits the screen.

We'll need a different strategy for limiting prefetch data transfer in
fast network conditions, perhaps by tracking and throttling the overall
bitrate.
2025-01-14 12:31:06 -05:00
Andrew Clark 7b61470177 [Segment Cache] Add act-inspired internal router testing helper (#74668)
Adds an internal test utility for e2e testing of requests initiated by
the Next.js Router, such as prefetches and navigations. Calls the given
async function then intercepts any router requests that are initiated as
a result. It will then wait for all the requests to complete before
exiting. Inspired by the React `act` API.

I generally dislike early testing abstractions, but getting all the
details right for intercepting the requests with Playwright ended up
being non-trivial enough that I relented and extracted it to a separate
module, in the `e2e/app-dir/segment-cache` folder. I'll hold off moving
it to somewhere even more generic until it's proven to be useful.

Example:

```js
// Asserts that rendering a link results in a prefetch response that
// contains some expected substring
await act(async () => {
  await revealLink()
}, {
  includes: 'subset of linked page content'
})
```

A few goals here:

- As much as possible, avoid coupling to internal implementation
details. For example, it will check for the presence of the "rsc" header
to determine if a request was initiated by the router, but that's about
it.
- No timers, no race conditions. It works by intercepting the requests
at the Playwright network layer. There should be no long pauses when
running the tests, and if an assertion fails, it should fail quickly.
- Account for Next.js's internal bandwidth throttling. The router does
not initiate more than a fixed number of requests at a time, so every
time a request is fulfilled, it has the potential to unblock more
prefetches. So we must wait a task to check for additional requests.
This is similar to what React's `act` implementation does to account for
asynchronous rendering tasks.
2025-01-09 10:26:59 -05:00
Andrew Clark ae1b9b3b54 [Segment Cache] Skip prefetched segments on server (#73626)
Based on:
- #73540

---

Currently if you navigate to a partially static route, the server will
always start rendering at the first segment that's not present on the
previous page. However, it should really start rendering at the first
*dynamic* segment — if the client has already prefetched a segment, and
it's fully static, there's no reason to render it again during the
dynamic server render.

We can do this by sending a more specific Next-Router-State-Tree request
header. Rather than send a tree that represents the previous route, we
sent the tree of the target route, but with a `refetch` marker added to
the first dynamic segment. (Without the refetch marker, the server would
send back an empty response.) This is determined by diffing against both
the previous route *and* the prefetch cache.

For now, this only works up to the first dynamic segment inside the new
subtree; once the server starts rendering along a path, it renders
everything else along that path. We could improve this in the future to
also omit static segments that appear inside a dynamic layout, though
this would likely require a change to the Next-Router-State-Tree
protocol.
2024-12-12 16:09:49 +00:00
Jiachi Liu f92b159e57 test: fix type error in segment-cache-basic test (#73755)
Fix type error introduced in #73540
2024-12-10 11:42:49 -08:00
Andrew Clark eecc5f1c86 [Segment Cache] Skip dynamic request if possible (#73540)
During a navigation, if all the data has been prefetched, and the target
route does not contain any dynamic data, then we should skip a request
to the server.

This uses the `isPartial` field I added in the previous PRs to track
whether the prefetched data is complete or not.
2024-12-10 13:34:05 -05:00
Andrew Clark 3eb1d035a6 Fix: Prefetching lazily generated param (#73715)
Fixes an oversight in #72168 where the segment data was not correctly
transferred from the render result to the cache entry in the case where
a prerender is lazily generated after build.

I also took the opportunity to remove one of the intermediate types used
by the various layers that the segment data passes through. For some
reason, in the original PR I made the type of `segmentData` on
CachedAppPageValue an object while the corresponding type on
AppPageRenderResultMetadata was a map. (I didn't really notice before
because in the case where the entry is generated at build time, it gets
written to disk then read back out, so there's some data conversion
happening anyway.)
2024-12-10 03:22:41 +00:00
Andrew Clark d8b6bd8892 [Segment Cache] Interception routes (#73434)
Implements prefetching support for interception routes using the Segment
Cache.

The overall flow is the same as the previous prefetch cache
implementation. If a page varies based on the Next-URL — in other words,
if it might possibly be intercepted — we include the Next-URL as part of
the cache key. However, since most pages do not vary on the Next-URL,
and this is known at build time, for most pages we can omit the Next-URL
from the cache key for all but the first request.

We do this by checking the Vary header of the first response, and if the
Next-URL is not included, we re-key the cache entry to remove the
Next-URL. All subsequent requests for the same page will match this
entry regardless of the Next-URL.

---

One difference from the previous prefetch cache implementation: when an
entry varies by Next-URL, rather than concatenating the Next-URL to the
href to create a combined cache key, we store the entries in a tiered
map structure whose keys are tuples of the href and Next-URL. Then we
compare each key part separately. This might end up being overkill but
it's nice because we don't have to worry about escaping the values, nor
do we have to store an encoded cache key separately from its individual
parts. We will likely use the same approach for storing segment cache
entries, which vary on both the segment path and (in some cases; not yet
implemented) the search params.
2024-12-06 11:33:28 -05:00
Andrew Clark 0344392f8e [Segment Cache] Initial implementation (#72875)
Based on:

- #72874 
- #72890 
- #72872 

---

This adds an initial implementation of the client Segment Cache, behind
the experimental `clientSegmentCache` flag. (Note: It is not anywhere
close to being ready for production use. It will take a while for it to
reach parity with the existing implementation.)

I've discussed the motivation in previous PRs, but I'll share a brief
summary here again:

The client Segment Cache is a rewrite of App Router's client caching
implementation, designed with PPR and "use cache" in mind. Its main
distinguishing feature from the current implementation is that it
fetches/caches/expires data per route segment, rather than per full URL.
An example of what this means in practical terms is that shared layouts
are deduplicated in the cache, resulting in less bandwidth. There are
other benefits we have in mind but that's the starting point.

I've tried to extract the work here into reasonably-sized commits (many
of which have already landed) but this one here is sorta unavoidably
large. Here are the main pieces:

-
[segment-cache/cache.ts](https://github.com/acdlite/next.js/blob/initial-implementation-client-segment-cache/packages/next/src/client/components/segment-cache/cache.ts):
This module is where the cache entries are maintained in memory. An
important design principle is that you must be able to read from the
cache synchronously without awaiting any promises. We avoid the use of
async/await wherever possible; instead, async tasks write their results
directly into the cache. This also helps to avoid race conditions.

Currently there's no eviction policy other than stale time, but
eventually we'll use an LRU for memory management.

-
[segment-cache/scheduler.ts](https://github.com/acdlite/next.js/blob/initial-implementation-client-segment-cache/packages/next/src/client/components/segment-cache/scheduler.ts):
This module is primarily a task scheduler. It's also used to manage
network bandwidth. The design is inspired by React Suspense and Rust
Futures — tasks are pull-based, not push-based. The backing data
structure is a MinHeap/PriorityQueue, to support efficient
reprioritization of tasks.

-
[segment-cache/navigation.ts](https://github.com/acdlite/next.js/blob/initial-implementation-client-segment-cache/packages/next/src/client/components/segment-cache/navigation.ts):
This module is responsible for creating a snapshot of the cache at the
time of a navigation. Right now it's mostly a bunch of glue code to
interop with the data structures used by the rest of the App Router,
like CacheNodeSeedData and FlightRouterState. The long term plan is to
move everything to using the Segment Cache and refactoring those data
structures.

Additional explanations are provided inline.
2024-11-20 15:23:00 -05:00
Andrew Clark 0402ced70e [Segment Cache] Implement behavior on cache miss (#72841)
In the current navigation implementation, a partially dynamic navigation
always does two separate requests: one for the static data, and one for
the dynamic data. Typically the static data is prefetched before the
navigation begins, but even in the case where it is not, the current
implementation will still fetch it first. It then wait to send a dynamic
request until the first chunk is received from the prefetch response,
leading to an unfortunate request waterfall.

In the Segment Cache implementation, our plan is to never block a
navigation on prefetch data that isn't already populated in the cache.
Instead, in the case of a cache miss, we'll immediately start a dynamic
navigation and rely on the fact that the first thing the dynamic
response sends is the static PPR shell of the target page.

Because we'll always have this as a fallback behavior for cache misses,
it's a good starting point for the Segment Cache implementation. Then we
can start incrementally adding more and more features until we've
eventually reached/surpassed parity with the current implementation.

---

To avoid duplication of logic, I've chosen to model cache misses as a
special case of the normal static + dynamic flow. We can pretend that
the route tree returned by the dynamic request is, in fact, the result
of a prefetch. Then we use that same server response to write data into
the CacheNode tree. So it's the same flow as the "happy path", except we
use a single server response for both stages.
2024-11-15 17:14:39 -05:00