next
Version:
The React Framework
173 lines (172 loc) • 9.5 kB
TypeScript
import type { DynamicParamTypesShort, PrefetchHints } from '../../shared/lib/app-router-types';
import type { VaryParamsIterable } from '../../shared/lib/segment-cache/vary-params-decoding';
import type { ManifestNode } from '../../build/webpack/plugins/flight-manifest-plugin';
import { type SegmentRequestKey } from '../../shared/lib/segment-cache/segment-value-encoding';
export type RootTreePrefetch = {
buildId?: string;
tree: TreePrefetch;
staleTime: number;
};
export type TreePrefetchParam = {
type: DynamicParamTypesShort;
key: string | null;
siblings: readonly string[] | null;
};
export type TreePrefetch = {
name: string;
param: TreePrefetchParam | null;
slots: null | {
[parallelRouteKey: string]: TreePrefetch;
};
/** Bitmask of PrefetchHint flags for this segment and its subtree */
prefetchHints: number;
};
/**
* Top-level response for a segment prefetch request. Contains the build ID
* and an array of segment data (one per segment in the bundle).
*
* Ordering contract: data[0] is the requested (terminal) segment. Subsequent
* elements are ancestors that were inlined into this response, built by
* walking the SegmentBundleNode linked list. The client's SegmentBundle
* linked list is constructed in the same order during scheduling, so the
* two are walked in parallel when the response arrives. A null element
* indicates a disabled segment (prefetch: 'force-disabled') that occupies a
* slot but carries no data. (Allow-runtime segments get real slots — the
* server emits static data for them unconditionally.)
*/
export type SegmentPrefetchResponse = {
buildId: string;
data: Array<SegmentPrefetch | null>;
/**
* True if this response was generated from a fallback shell render (i.e. the
* page had not yet been prerendered with concrete params, so it was rendered
* with `fallbackRouteParams`). The client uses this to schedule a retry,
* since a more complete version may become available once the server's
* background regeneration finishes.
*
* Note: this is distinct from per-segment `isPartial`. A fully-prerendered
* PPR page can have partial segments (dynamic holes filled by runtime
* requests); those should not be retried. `isUpgradeableISRFallback` specifically means a
* more complete *static* version may become available.
*/
isUpgradeableISRFallback: boolean;
/**
* Shell byte boundary — the segment-level analogue of the route-level `a`.
* A promise because the value is only known mid-stream; its resolution row
* flushes past the boundary, so a truncated shell decode reads it as
* pending, which is harmless.
*
* - `> 0`: byte offset such that re-decoding the response truncated there
* yields the shell variant of every segment — param-dependent content
* reduced to still-pending references (which render as the param fallback).
* - `null`: the shell is the full response; no separate decode needed.
* - `0`: no shell (the page wasn't produced by staged rendering). Never a
* valid offset — an envelope always has bytes — so it doubles as the
* "none" sentinel.
*/
a: Promise<number | null>;
/**
* Root params accessed anywhere in this response, emitted once here rather
* than folded into each segment's `varyParams`, mirroring the route-level
* response's split of `r` from per-segment params. The client unions them
* back in via `readVaryParams`.
*/
rootVaryParams: VaryParamsIterable | null;
/**
* The page's runtime-data-access flag (the page payload's `u`), forwarded
* from the staged decode of the page data: whether the prerender accessed
* a data source that would have resolved during a runtime prerender
* (cookies, headers, fallback params, searchParams, ...). The flag is
* monotonic (false → true, at most once), so a promise suffices; the
* client takes the settled value visible in its decode. Fulfilled `true`
* means a runtime prefetch would return more than this static response;
* pending or fulfilled `false` means it wouldn't. The answer is rewindable
* because the fulfillment row lands on the same side of the shell byte
* boundary (`a`) as the access it records: a decode truncated at `a` reads
* pending for a post-shell access, i.e. `false` for the shell variant.
*
* Tracking is page-global, so this lives on the response envelope, not on
* each segment. (A pending promise also costs Flight no abort listener on
* the render, unlike an async iterable, which holds one for as long as
* it's open.) Per-segment granularity comes from combining it with each
* segment's `isPartial`:
*
* needsRuntimeRequest(segment) = (settled true) && (isPartial pending)
*
* A segment whose `isPartial` promise fulfilled is fully static, and a
* fully static segment gains nothing from a runtime request no matter what
* the page accessed. Conversely, a partial segment on a page that accessed
* no runtime data also reads `false`: its holes come from sources that hang
* in a runtime prerender too (`io()`, `connection()`, uncached IO), and are
* only filled by the navigation-time dynamic request.
*
* The derived value must never falsely claim that no runtime request is
* needed, so every fallback is conservative: pages that carry no `u`
* (legacy render paths) forward an already-resolved `true`. Unlike the
* build-constant prefetch hints (including the tree-level
* ShouldAttemptStaticPrefetch), this is computed per render and may change
* between responses for the same build — it reflects THIS response.
*/
needsRuntimeRequest: Promise<boolean>;
};
export type SegmentPrefetch = {
rsc: React.ReactNode | null;
/**
* Fulfilled once the segment is known to be fully static. A partial segment
* (dynamic holes a runtime request must fill) leaves this pending forever —
* the same way Flight encodes the holes themselves — so the client reads a
* pending `isPartial` as "partial". The fulfillment row, when there is one,
* flushes past the shell boundary, so a truncated shell decode also reads
* as partial: correct, since a shell has holes by construction.
*
* This monotonic pending → fulfilled encoding also serves as the
* per-segment half of the needs-runtime-request derivation — see
* `SegmentPrefetchResponse['needsRuntimeRequest']`.
*/
isPartial: Promise<void>;
/**
* The segment's stale time in seconds, forwarded as an async iterable for
* the same reason as the route-level `InitialRSCPayload.s`: its final value
* is only known late in the stream, and the async-iterable form survives a
* truncated/rewound shell decode (read via thenable status from the
* buffered response). The client takes the last yielded value.
*/
staleTime: AsyncIterable<number>;
/**
* The params this segment's own output depends on (not including the
* response-level root params — see `rootVaryParams`). Forwarded as an async
* iterable because, like the route-level response's params, the values are
* only known late in the stream. The client cache keys reusable entries on
* these.
* - `null`: not tracked; conservatively assume all params matter.
* - yields nothing: no params accessed; reusable for any param values.
*/
varyParams: VaryParamsIterable | null;
};
export declare function collectSegmentData(isCacheComponentsEnabled: boolean, fullPageDataBuffer: Buffer, staleTime: number, clientModules: ManifestNode, serverConsumerManifest: any, prefetchInlining: boolean, hints: PrefetchHints | null, isUpgradeableISRFallback: boolean): Promise<Map<SegmentRequestKey, Buffer>>;
/**
* Compute prefetch hints for a route by measuring segment sizes and deciding
* which segments should be inlined. Only runs at build time. The results are
* written to prefetch-hints.json and loaded at server startup.
*
* This is a separate pass from collectSegmentData so that the inlining
* decisions can be fed back into collectSegmentData to control which segments
* are output as separate entries vs. inlined into their parent.
*
* `shouldAttemptStaticPrefetch` (computed by the caller from the prerender's
* runtime-data tracking) is folded onto every node of the result, so the
* manifest delivers it to every response like the other hint bits. It's
* independent of the inlining feature: when `inlining` is false the sizing
* pass is skipped entirely — no inlining bits are emitted — and only the
* tree shape carrying the static-prefetch hint is built.
*
* Both kinds of hint have the same structure and the same lifetime — one
* bitmask per node of the route tree, measured once per build and constant
* for the deployment. They differ only in what they're derived from: the
* inlining bits from the size of each segment's encoded response, the
* static-prefetch bit from what the decoded body turned out to access.
*/
export declare function collectPrefetchHints(fullPageDataBuffer: Buffer, staleTime: number, clientModules: ManifestNode, serverConsumerManifest: any, inlining: {
maxSize: number;
maxBundleSize: number;
} | false, shouldAttemptStaticPrefetch: boolean): Promise<PrefetchHints>;