Skip to content

Commit dd72998

Browse files
committed
Use prefetched segment data on initial render
When PPR is enabled, there are two different versions of a segment's data we can render: a static version (that may contain holes for the dynamic parts) and a full version that contains everything. The static version is prefetched before navigation, and the full version is only loaded once the navigation occurs. Both versions are stored on the Cache Node object. Inside LayoutRouter, we must choose which version to render. We'll use an experimental feature of React's `useDeferredValue` hook: facebook/react#27500 ```js const dataToRender = useDeferredValue(fullData, prefetchedData) ``` React will coordinate when to switch from the prefetch data to the full data. For example, if the prefetch data is unable to finish rendering (this would happen if the segment contained dynamic data that was not wrapped in a Suspense boundary), it knows to switch to the full data even though the first render did not commit.
1 parent 29f7236 commit dd72998

File tree

1 file changed

+31
-7
lines changed

1 file changed

+31
-7
lines changed

packages/next/src/client/components/layout-router.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import type {
1212
import type { ErrorComponent } from './error-boundary'
1313
import type { FocusAndScrollRef } from './router-reducer/router-reducer-types'
1414

15-
import React, { useContext, use, startTransition, Suspense } from 'react'
15+
import React, {
16+
useContext,
17+
use,
18+
startTransition,
19+
Suspense,
20+
useDeferredValue,
21+
} from 'react'
1622
import ReactDOM from 'react-dom'
1723
import {
1824
LayoutRouterContext,
@@ -358,12 +364,30 @@ function InnerLayoutRouter({
358364
childNodes.set(cacheKey, newLazyCacheNode)
359365
}
360366

361-
// `rsc` represents the renderable node for this segment. It's either a
362-
// React node or a promise for a React node, except we special case `null` to
363-
// represent that this segment's data is missing. If it's a promise, we need
364-
// to unwrap it so we can determine whether or not the data is missing.
365-
const rsc: any = childNode.rsc
366-
const resolvedRsc =
367+
// `rsc` represents the renderable node for this segment.
368+
369+
// If this segment has a `prefetchRsc`, it's the statically prefetched data.
370+
// We should use that on initial render instead of `rsc`. Then we'll switch
371+
// to `rsc` when the dynamic response streams in.
372+
//
373+
// If no prefetch data is available, then we go straight to rendering `rsc`.
374+
const resolvedPrefetchRsc =
375+
childNode.prefetchRsc !== null ? childNode.prefetchRsc : childNode.rsc
376+
377+
// We use `useDeferredValue` to handle switching between the prefetched and
378+
// final values. The second argument is returned on initial render, then it
379+
// re-renders with the first argument.
380+
//
381+
// @ts-expect-error The second argument to `useDeferredValue` is only
382+
// available in the experimental builds. When its disabled, it will always
383+
// return `rsc`.
384+
const rsc: any = useDeferredValue(childNode.rsc, resolvedPrefetchRsc)
385+
386+
// `rsc` is either a React node or a promise for a React node, except we
387+
// special case `null` to represent that this segment's data is missing. If
388+
// it's a promise, we need to unwrap it so we can determine whether or not the
389+
// data is missing.
390+
const resolvedRsc: React.ReactNode =
367391
typeof rsc === 'object' && rsc !== null && typeof rsc.then === 'function'
368392
? use(rsc)
369393
: rsc

0 commit comments

Comments
 (0)