Skip to content

Commit

Permalink
Use prefetched segment data on initial render
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acdlite committed Dec 18, 2023
1 parent 9cd3d65 commit 27ca4a2
Showing 1 changed file with 30 additions and 6 deletions.
36 changes: 30 additions & 6 deletions packages/next/src/client/components/layout-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ import type {
import type { ErrorComponent } from './error-boundary'
import type { FocusAndScrollRef } from './router-reducer/router-reducer-types'

import React, { useContext, use, startTransition, Suspense } from 'react'
import React, {
useContext,
use,
startTransition,
Suspense,
useDeferredValue,
} from 'react'
import ReactDOM from 'react-dom'
import {
LayoutRouterContext,
Expand Down Expand Up @@ -358,11 +364,29 @@ function InnerLayoutRouter({
childNodes.set(cacheKey, newLazyCacheNode)
}

// `rsc` represents the renderable node for this segment. It's either a
// React node or a promise for a React node, except we special case `null` to
// represent that this segment's data is missing. If it's a promise, we need
// to unwrap it so we can determine whether or not the data is missing.
const rsc: any = childNode.rsc
// `rsc` represents the renderable node for this segment.

// If this segment has a `prefetchRsc`, it's the statically prefetched data.
// We should use that on initial render instead of `rsc`. Then we'll switch
// to `rsc` when the dynamic response streams in.
//
// If no prefetch data is available, then we go straight to rendering `rsc`.
const resolvedPrefetchRsc =
childNode.prefetchRsc !== null ? childNode.prefetchRsc : childNode.rsc

// We use `useDeferredValue` to handle switching between the prefetched and
// final values. The second argument is returned on initial render, then it
// re-renders with the first argument.
//
// @ts-expect-error The second argument to `useDeferredValue` is only
// available in the experimental builds. When its disabled, it will always
// return `rsc`.
const rsc: any = useDeferredValue(childNode.rsc, resolvedPrefetchRsc)

// `rsc` is either a React node or a promise for a React node, except we
// special case `null` to represent that this segment's data is missing. If
// it's a promise, we need to unwrap it so we can determine whether or not the
// data is missing.
const resolvedRsc =
typeof rsc === 'object' && rsc !== null && typeof rsc.then === 'function'
? use(rsc)
Expand Down

0 comments on commit 27ca4a2

Please sign in to comment.