mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
During prerendering, track which route params each segment accesses. Params that are NOT accessed can be omitted from the client router's cache key for that segment, allowing it to be shared across multiple pages and reducing the number of prefetch requests. For example, if a page at /shop/[category]/[itemId] only accesses `category` but not `itemId`, navigating between /shop/electronics/phone and /shop/electronics/tablet can reuse the same cached page segment, since only `category` affects its output. Most of the client-side changes were already implemented in previous PRs; the core mechanism was already being used for omitting search params from the cache keys of static segments. This generalizes the mechanism for all params. So this PR is primarily about adding per-param tracking on the server and wiring up the logic to send the params to the client. Each segment's CacheNodeSeedData contains its own VaryParamsThenable that resolves to a Set<string> of accessed param names. The thenable is a mutable tracker during render that accumulates param accesses, then resolves when rendering completes. The response also includes an `h` field for head vary params, which tracks params accessed by generateMetadata/generateViewport separately from segment body access. The trickiest part of this implementation is the timing. The vary params thenables represent metadata about the response, but they're also part of the response itself - they're streamed alongside the segment data. We can't know which params were accessed until rendering completes, but we need to resolve all thenables before aborting the stream, or else the client would block waiting for data that will never arrive. We address this on both sides. On the server, we resolve all thenables immediately before aborting. On the client, we read vary params synchronously using a React Flight optimization: calling thenable.then(noop) forces Flight to transition from 'resolved_model' to 'fulfilled' without scheduling a microtask. If the thenable still isn't fulfilled after this, we fall back to null (unknown) rather than blocking. This ensures the client never suspends on these thenables, providing a safe fallback if something goes wrong with server timing. A key distinction is null vs empty Set. An empty Set means the segment accesses no params and can be shared across all param values - this is the case for client components (when Cache Components is enabled), segments without user code, etc. A null value means tracking failed - either because it's not wired up yet (like runtime prefetches), or due to some edge case where the thenables weren't resolved in time. For segments where we know upfront that no params will be accessed, we use a singleton emptyVaryParamsTracker that's already resolved. This ensures these segments resolve correctly even if other tracking fails. In the future, we'll likely optimize the response format by sending a bitmask instead of a Set of param names. But this is mostly just an optimization — sending the Set doesn't expose anything new since the param names are already embedded in the route tree (although that's also something we could obfuscate in the future). This does not yet implement vary param tracking during runtime prefetches - the abort timing needs additional coordination. Deferred to a separate PR.
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { LinkAccordion } from '../../../components/link-accordion'
|
|
|
|
/**
|
|
* Index page for the page segment reuse test.
|
|
*
|
|
* This test demonstrates a non-obvious case: a page segment can be reused when
|
|
* it doesn't access a param that the layout DOES access. This is the inverse
|
|
* of the typical pattern where layouts are reused while pages vary.
|
|
*
|
|
* Setup:
|
|
* - Layout accesses BOTH `category` AND `item` → varies on both
|
|
* - Page accesses ONLY `category` → varies only on category
|
|
*
|
|
* When navigating between routes that share the same category but different
|
|
* items (e.g., /electronics/phone → /electronics/tablet), the layout must be
|
|
* re-fetched (it accesses `item`), but the page is a cache hit (it only
|
|
* accesses `category`, which hasn't changed).
|
|
*
|
|
* Manual testing:
|
|
* 1. Click checkbox for "phone" — layout and page both fetched
|
|
* 2. Click checkbox for "tablet" — layout re-fetched, page is cache hit
|
|
*/
|
|
export default function PageReuseIndexPage() {
|
|
return (
|
|
<div id="page-reuse-index">
|
|
<h1>Page Segment Reuse Test</h1>
|
|
<p>
|
|
Tests the inverse of typical layout reuse: the page segment can be
|
|
reused when it doesn't access a param that the layout does access.
|
|
</p>
|
|
<p>
|
|
<strong>Layout:</strong> accesses both category and item
|
|
<br />
|
|
<strong>Page:</strong> accesses only category
|
|
</p>
|
|
<ul>
|
|
<li>
|
|
<LinkAccordion href="/page-reuse/electronics/phone">
|
|
Electronics: Phone
|
|
</LinkAccordion>
|
|
</li>
|
|
<li>
|
|
<LinkAccordion href="/page-reuse/electronics/tablet">
|
|
Electronics: Tablet
|
|
</LinkAccordion>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
)
|
|
}
|