mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
`cachedNavigations` is an under-the-hood optimization that caches subsets of a route, seeded from actual navigations, so subsequent navigations to the same or similar page are served instantly. It is on by default with Cache Components. The static stage is automatic, while the more expensive runtime stage (which spawns an additional prerender and embeds its output in the response) is opted into per segment via `export const prefetch = 'allow-runtime'`. Gating the runtime stage on the `prefetch` config is awkward, because Cached Navigations is about caching real navigations rather than speculative prefetching, so the people who benefit most are exactly those who turn prefetching down. To dogfood the runtime stage's CPU and payload overhead across a whole app, this change widens the `experimental.cachedNavigations` config from `boolean` to `boolean | 'allow-runtime'`. `true` keeps its current meaning, and `'allow-runtime'` additionally treats every route as runtime-cached regardless of its per-segment `prefetch` config. The new value only affects the two production spawn gates in `app-render.tsx` (the client-navigation Flight render and the initial HTML render), where the runtime prerender is now spawned when either a segment opted in or the global flag is set. Prefetch hints, instant validation, and component-tree staging are intentionally left keyed off the per-segment config. The widened value is preserved through the render options (the build templates pass it through instead of coercing it to a boolean), while the client-facing env var stays a plain boolean since the runtime prerender is entirely server-driven. The `cached-navigations` e2e fixture is split into a `default/` fixture (the existing suite, re-pointed) and a new `global-runtime/` fixture that sets `cachedNavigations: 'allow-runtime'`, with a test asserting that a route with no `prefetch` export still has its request-derived content runtime-cached on a second navigation.
88 lines
2.1 KiB
TypeScript
88 lines
2.1 KiB
TypeScript
import { cacheLife } from 'next/cache'
|
|
import { cookies, headers } from 'next/headers'
|
|
import { connection } from 'next/server'
|
|
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
|
|
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()
|
|
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'
|
|
return (
|
|
<p>
|
|
Header: {value} ({new Date().toISOString()})
|
|
</p>
|
|
)
|
|
}
|
|
|
|
async function ConnectionContent() {
|
|
await connection()
|
|
return <p>Dynamic content ({new Date().toISOString()})</p>
|
|
}
|