### Why
Most viewport prefetches are scheduled via a shared
`IntersectionObserver`. When several links enter the viewport at once
(e.g. on initial load), the observer reports them in document order and
we scheduled them in that order. Because the prefetch scheduler gives
the highest priority to the *most recently scheduled* task, this meant
the link **lowest** in the document ended up with the highest priority —
the opposite of a sensible default.
### What
Iterate each observer batch in reverse, so the link nearest the **top**
of the document is scheduled last and therefore prioritized. The topmost
link isn't guaranteed to be the most important, but as a default
heuristic it's more reasonable than favoring whichever link happens to
be lowest in the document.
Also updates the two hover tests in `prefetch-scheduling`, whose
expected ordering was coupled to the old bottom-first default (the set
of route trees prefetched within the concurrency limit flips from pages
7,6,5,4 to 1,2,3,4). The behaviors they assert are unchanged, and a new
test covers the top-of-document prioritization.
---
Supersedes #94902, which was opened from a fork. The "Test new and
changed tests when deployed" jobs cannot pass on fork PRs — `vercel
link` fails with "No existing credentials found" because
`secrets.VERCEL_ADAPTER_TEST_TOKEN` is not available to `pull_request`
runs from forks. This PR is the same change (rebased onto latest canary)
on an upstream branch so the deploy jobs get credentials.
<!-- NEXT_JS_LLM_PR -->
Flips the `experimental.prefetchInlining` default from `false` to
`true`. This is the first step toward removing the flag entirely.
Tests that explicitly set `prefetchInlining: true` have been cleaned up
since they now match the default. Any CI failures from this PR will be
addressed in follow-up PRs by temporarily opting out individual tests
with `prefetchInlining: false` until they are migrated.
<!-- NEXT_JS_LLM_PR -->
When links remount (e.g. toggling between tabs), the prefetch scheduler
processes tasks across multiple async batches due to network bandwidth
limits. Between batches, LRU cleanup could fire and evict cache entries
for tasks that hadn't been processed yet. Those tasks would then see
cache misses, fetch fresh data, push the LRU over the limit again, and
trigger more cleanup, cascading through nearly all entries.
We fix this by moving LRU cleanup into the prefetch scheduler loop.
Instead of scheduling cleanup independently via
`requestIdleCallback`/`setTimeout`, the LRU module pings the scheduler
when the cache exceeds its size limit, and the scheduler runs cleanup
only when it's fully idle (no queued tasks, no in-flight requests). At
that point, all active prefetch tasks have finished reading from the
cache, so eviction targets genuinely stale data.
The tradeoff is that the cache can temporarily exceed its size limit
while prefetch tasks are in flight. This was already the case since
cleanup was always async, but it's now more explicit since cleanup is
tied to the scheduler settling rather than the next idle callback.
Follow-up to #89610.
Adds an e2e test that detects linear heap growth caused by unclosed
prefetch response streams. The test cycles through mounting/unmounting
links to trigger repeated prefetches and LRU evictions, then uses CDP
heap profiling with linear regression to verify the heap plateaus rather
than growing steadily.

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.
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.
## 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
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.
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`
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.
Based on:
- #73434
---
This implements an LRU for data stored in the Segment Cache.
For now I've hardcoded in a cache size of 50mb, but as a follow up, we
should look into creating a dynamic limit based on device memory
constraints.
Segments and Route entries are managed by separate LRUs. We could
combine them into a single LRU, but because they are separate types,
we'd need to wrap each one in an extra LRU node (to maintain
monomorphism, at the cost of additional memory).