|
| 1 | +import { cookies } from 'next/headers' |
| 2 | +import { Suspense } from 'react' |
| 3 | +import { unstable_navigation } from 'next/stages' |
| 4 | + |
| 5 | +// Opt this route into runtime prefetching. unstable_navigation() only has an |
| 6 | +// observable effect during runtime prefetches (it's a no-op during static |
| 7 | +// prerendering), so the route must be runtime-prefetch-enabled for the |
| 8 | +// exclusion to be testable. |
| 9 | +export const prefetch = 'allow-runtime' |
| 10 | + |
| 11 | +// Sample cookie values used at build time to validate the runtime prefetch. |
| 12 | +export const instant = { |
| 13 | + unstable_samples: [{ cookies: [{ name: 'testCookie', value: 'testValue' }] }], |
| 14 | +} |
| 15 | + |
| 16 | +// This page demonstrates the canonical pattern for combining |
| 17 | +// unstable_navigation() with "use cache". Awaiting unstable_navigation() |
| 18 | +// inside a cache scope is an error, so keep the unstable_navigation() gate in |
| 19 | +// an uncached wrapper and put the "use cache" directive on inner functions |
| 20 | +// called below the gate. Cached content that isn't behind the gate is still |
| 21 | +// included in runtime prefetches. |
| 22 | +export default function Page() { |
| 23 | + return ( |
| 24 | + <main> |
| 25 | + <Suspense |
| 26 | + fallback={<div id="cookie-fallback">Loading cookie content...</div>} |
| 27 | + > |
| 28 | + <CookieSection /> |
| 29 | + </Suspense> |
| 30 | + </main> |
| 31 | + ) |
| 32 | +} |
| 33 | + |
| 34 | +// Reads cookies above the `await unstable_navigation()` gate, so a runtime |
| 35 | +// prefetch of this page actually renders runtime content. Everything below is |
| 36 | +// nested under the cookie read, so none of it can appear in a static prefetch |
| 37 | +// (a static prerender suspends at cookies() before reaching it) — which lets |
| 38 | +// the test assert unambiguously on the runtime prefetch response. |
| 39 | +async function CookieSection() { |
| 40 | + const cookieStore = await cookies() |
| 41 | + const cookieValue = cookieStore.get('testCookie')?.value ?? null |
| 42 | + return ( |
| 43 | + <> |
| 44 | + <div id="cookie-value">{`Workaround cookie: ${cookieValue}`}</div> |
| 45 | + <Suspense |
| 46 | + fallback={<div id="visible-fallback">Loading cached visible...</div>} |
| 47 | + > |
| 48 | + <CachedVisible /> |
| 49 | + </Suspense> |
| 50 | + <Suspense fallback={<div id="gated-fallback">Loading gated...</div>}> |
| 51 | + <GatedSection /> |
| 52 | + </Suspense> |
| 53 | + </> |
| 54 | + ) |
| 55 | +} |
| 56 | + |
| 57 | +// Cached, not gated: included in runtime prefetches. |
| 58 | +async function CachedVisible() { |
| 59 | + 'use cache' |
| 60 | + return <div id="cached-visible">Cached visible content</div> |
| 61 | +} |
| 62 | + |
| 63 | +// Uncached wrapper: gate first, then read the cached data. Only this subtree |
| 64 | +// is excluded from the runtime prefetch and deferred to the navigation. |
| 65 | +async function GatedSection() { |
| 66 | + await unstable_navigation() |
| 67 | + const data = await getWorkaroundData() |
| 68 | + return <div id="gated-data">{data}</div> |
| 69 | +} |
| 70 | + |
| 71 | +async function getWorkaroundData() { |
| 72 | + 'use cache' |
| 73 | + return 'Cached workaround data' |
| 74 | +} |
0 commit comments