mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
When a partially static page is loaded via initial HTML (PPR resume), the RSC payload now includes the `l` (static stage byte length) field, enabling the client router to extract and cache the static stage during hydration. Subsequent navigations to the same page serve the cached static content instantly while streaming in dynamic content. On the server, the resume path in `renderToStream` now uses staged rendering (mirroring `generateStagedDynamicFlightRenderResult`) when Cache Components is enabled. A `StagedRenderingController` separates the static and dynamic stages, and `countStaticStageBytes` resolves the byte length promise that Flight serializes into the stream as `l`. `getRSCPayload` accepts `staleTimeIterable` and `staticStageByteLengthPromise` options to wire `s` and `l` into the `InitialRSCPayload`. On the client, `app-index.tsx` tees the inlined Flight stream when Cache Components is enabled. One copy goes to React for decoding, the other is passed to `createInitialRouterState`. When `l` is present (partially static), the clone is truncated at the byte boundary via `decodeStaticStage` and written into the segment cache via `writeStaticStageResponseIntoCache` with `isResponsePartial: true`. When `l` is absent but `s` is present (fully static), the same function is used with `isResponsePartial: false`. `createInitialRouterState` now accepts a single `initialRSCPayload: InitialRSCPayload` prop instead of individual destructured fields.
93 lines
2.2 KiB
TypeScript
93 lines
2.2 KiB
TypeScript
import { cacheLife } from 'next/cache'
|
|
import { cookies, headers } from 'next/headers'
|
|
import { connection } from 'next/server'
|
|
import { setTimeout } from 'timers/promises'
|
|
import { Suspense } from 'react'
|
|
|
|
export default async function Page({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ q?: string }>
|
|
}) {
|
|
return (
|
|
<main>
|
|
<CachedContent />
|
|
<div id="search-params-boundary">
|
|
<Suspense fallback={<p>Loading search params...</p>}>
|
|
<SearchParamsContent searchParams={searchParams} />
|
|
</Suspense>
|
|
</div>
|
|
<div id="cookies-boundary">
|
|
<Suspense fallback={<p>Loading cookies...</p>}>
|
|
<CookiesContent />
|
|
</Suspense>
|
|
</div>
|
|
<div id="headers-boundary">
|
|
<Suspense fallback={<p>Loading headers...</p>}>
|
|
<HeadersContent />
|
|
</Suspense>
|
|
</div>
|
|
<div id="connection-boundary">
|
|
<Suspense fallback={<p>Loading connection...</p>}>
|
|
<ConnectionContent />
|
|
</Suspense>
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
async function CachedContent() {
|
|
'use cache'
|
|
cacheLife({ stale: 120 })
|
|
return <p id="cached-content">Cached content ({new Date().toISOString()})</p>
|
|
}
|
|
|
|
async function SearchParamsContent({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ q?: string }>
|
|
}) {
|
|
const { q } = await searchParams
|
|
await setTimeout(300)
|
|
return (
|
|
<p>
|
|
Search params: {q ?? 'none'} ({new Date().toISOString()})
|
|
</p>
|
|
)
|
|
}
|
|
|
|
async function CookiesContent() {
|
|
const cookieStore = await cookies()
|
|
const value = cookieStore.get('testCookie')?.value ?? 'none'
|
|
const date = await getShortLivedCachedDate()
|
|
await setTimeout(300)
|
|
return (
|
|
<p>
|
|
Cookie: {value}, Cached at: {date}
|
|
</p>
|
|
)
|
|
}
|
|
|
|
async function getShortLivedCachedDate() {
|
|
'use cache'
|
|
cacheLife({ stale: 30 })
|
|
return new Date().toISOString()
|
|
}
|
|
|
|
async function HeadersContent() {
|
|
const headerStore = await headers()
|
|
const value = headerStore.get('x-test-header') ?? 'none'
|
|
await setTimeout(300)
|
|
return (
|
|
<p>
|
|
Header: {value} ({new Date().toISOString()})
|
|
</p>
|
|
)
|
|
}
|
|
|
|
async function ConnectionContent() {
|
|
await connection()
|
|
await setTimeout(600)
|
|
return <p>Dynamic content ({new Date().toISOString()})</p>
|
|
}
|