Skip to content

Commit

Permalink
renew router cache after update from server
Browse files Browse the repository at this point in the history
  • Loading branch information
ztanner committed Feb 13, 2024
1 parent 4e03b85 commit ef85241
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 62 deletions.
13 changes: 2 additions & 11 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ import type {
CacheNode,
AppRouterInstance,
} from '../../shared/lib/app-router-context.shared-runtime'
import type {
FlightRouterState,
FlightData,
} from '../../server/app-render/types'
import type { ErrorComponent } from './error-boundary'
import {
ACTION_FAST_REFRESH,
Expand Down Expand Up @@ -178,17 +174,12 @@ function useChangeByServerResponse(
dispatch: React.Dispatch<ReducerActions>
): RouterChangeByServerResponse {
return useCallback(
(
previousTree: FlightRouterState,
flightData: FlightData,
overrideCanonicalUrl: URL | undefined
) => {
({ previousTree, serverResponse }) => {
startTransition(() => {
dispatch({
type: ACTION_SERVER_PATCH,
flightData,
previousTree,
overrideCanonicalUrl,
serverResponse,
})
})
},
Expand Down
7 changes: 5 additions & 2 deletions packages/next/src/client/components/layout-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,12 +417,15 @@ function InnerLayoutRouter({
* Flight response data
*/
// When the data has not resolved yet `use` will suspend here.
const [flightData, overrideCanonicalUrl] = use(lazyData)
const serverResponse = use(lazyData)

// setTimeout is used to start a new transition during render, this is an intentional hack around React.
setTimeout(() => {
startTransition(() => {
changeByServerResponse(fullTree, flightData, overrideCanonicalUrl)
changeByServerResponse({
previousTree: fullTree,
serverResponse,
})
})
})
// Suspend infinitely as `changeByServerResponse` will cause a different part of the tree to be rendered.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function applyFlightData(
existingCache: CacheNode,
cache: CacheNode,
flightDataPath: FlightDataPath,
wasPrefetched: boolean = false
hasReusablePrefetch: boolean = false
): boolean {
// The one before last item is the router state tree patch
const [treePatch, cacheNodeSeedData, head] = flightDataPath.slice(-3)
Expand All @@ -32,7 +32,7 @@ export function applyFlightData(
treePatch,
cacheNodeSeedData,
head,
wasPrefetched
hasReusablePrefetch
)
} else {
// Copy rsc for the root node of the cache.
Expand All @@ -47,7 +47,7 @@ export function applyFlightData(
cache,
existingCache,
flightDataPath,
wasPrefetched
hasReusablePrefetch
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function fillCacheWithNewSubTreeData(
newCache: CacheNode,
existingCache: CacheNode,
flightDataPath: FlightDataPath,
wasPrefetched?: boolean
hasReusablePrefetch?: boolean
): void {
const isLastEntry = flightDataPath.length <= 5
const [parallelRouteKey, segment] = flightDataPath
Expand Down Expand Up @@ -71,7 +71,7 @@ export function fillCacheWithNewSubTreeData(
flightDataPath[2],
seedData,
flightDataPath[4],
wasPrefetched
hasReusablePrefetch
)

childSegmentMap.set(cacheKey, childCacheNode)
Expand Down Expand Up @@ -99,6 +99,6 @@ export function fillCacheWithNewSubTreeData(
childCacheNode,
existingChildCacheNode,
flightDataPath.slice(2),
wasPrefetched
hasReusablePrefetch
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function fillLazyItemsTillLeafWithHead(
routerState: FlightRouterState,
cacheNodeSeedData: CacheNodeSeedData | null,
head: React.ReactNode,
wasPrefetched?: boolean
hasReusablePrefetch?: boolean
): void {
const isLastSegment = Object.keys(routerState[1]).length === 0
if (isLastSegment) {
Expand Down Expand Up @@ -59,7 +59,7 @@ export function fillLazyItemsTillLeafWithHead(
prefetchRsc: null,
parallelRoutes: new Map(existingCacheNode?.parallelRoutes),
}
} else if (wasPrefetched && existingCacheNode) {
} else if (hasReusablePrefetch && existingCacheNode) {
// No new data was sent from the server, but the existing cache node
// was prefetched, so we should reuse that.
newCacheNode = {
Expand Down Expand Up @@ -91,7 +91,7 @@ export function fillLazyItemsTillLeafWithHead(
parallelRouteState,
parallelSeedData ? parallelSeedData : null,
head,
wasPrefetched
hasReusablePrefetch
)

newCache.parallelRoutes.set(key, parallelRouteCacheNode)
Expand Down Expand Up @@ -133,7 +133,7 @@ export function fillLazyItemsTillLeafWithHead(
parallelRouteState,
parallelSeedData,
head,
wasPrefetched
hasReusablePrefetch
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,22 @@ function navigateReducer_noPPR(
}

const cache: CacheNode = createEmptyCacheNode()
const hasReusablePrefetch =
prefetchValues.kind === 'auto' &&
prefetchEntryCacheStatus === PrefetchCacheEntryStatus.reusable
let applied = applyFlightData(
currentCache,
cache,
flightDataPath,
prefetchValues.kind === 'auto' &&
prefetchEntryCacheStatus === PrefetchCacheEntryStatus.reusable
hasReusablePrefetch
)

if (!hasReusablePrefetch) {
// Once the prefetch entry no longer reusable, `applyFlightData` will signal to layout router that it needs to lazy fetch the data
// We update the `lastUsedTime` so that we renew the 30s cache for this entry
prefetchValues.lastUsedTime = Date.now()
}

if (
!applied &&
prefetchEntryCacheStatus === PrefetchCacheEntryStatus.stale
Expand Down Expand Up @@ -444,14 +452,22 @@ function navigateReducer_PPR(
// tree. Or in the meantime we could factor it out into a
// separate function.
const cache: CacheNode = createEmptyCacheNode()
const hasReusablePrefetch =
prefetchValues.kind === 'auto' &&
prefetchEntryCacheStatus === PrefetchCacheEntryStatus.reusable
let applied = applyFlightData(
currentCache,
cache,
flightDataPath,
prefetchValues.kind === 'auto' &&
prefetchEntryCacheStatus === PrefetchCacheEntryStatus.reusable
hasReusablePrefetch
)

if (!hasReusablePrefetch) {
// Once the prefetch entry no longer reusable, `applyFlightData` will signal to layout router that it needs to lazy fetch the data
// We update the `lastUsedTime` so that we renew the 30s cache for this entry
prefetchValues.lastUsedTime = Date.now()
}

if (
!applied &&
prefetchEntryCacheStatus === PrefetchCacheEntryStatus.stale
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,19 @@ describe('serverPatchReducer', () => {
],
])

const url = new URL('/linking/about', 'https://localhost') as any
const state = createInitialRouterState({
buildId,
initialTree,
initialHead: null,
initialCanonicalUrl,
initialSeedData: ['', {}, children],
initialParallelRoutes,
location: new URL('/linking/about', 'https://localhost') as any,
location: url,
})
const action: ServerPatchAction = {
type: ACTION_SERVER_PATCH,
flightData: flightDataForPatch,
serverResponse: [flightDataForPatch, undefined],
previousTree: [
'',
{
Expand All @@ -155,7 +156,6 @@ describe('serverPatchReducer', () => {
undefined,
true,
],
overrideCanonicalUrl: undefined,
}

const newState = await serverPatchReducer(state, action)
Expand Down Expand Up @@ -339,21 +339,22 @@ describe('serverPatchReducer', () => {
shouldScroll: true,
}

const url = new URL(initialCanonicalUrl, 'https://localhost') as any
const state = createInitialRouterState({
buildId,
initialTree,
initialHead: null,
initialCanonicalUrl,
initialSeedData: ['', {}, children],
initialParallelRoutes,
location: new URL(initialCanonicalUrl, 'https://localhost') as any,
location: url,
})

const stateAfterNavigate = await navigateReducer(state, navigateAction)

const action: ServerPatchAction = {
type: ACTION_SERVER_PATCH,
flightData: flightDataForPatch,
serverResponse: [flightDataForPatch, undefined],
previousTree: [
'',
{
Expand All @@ -368,7 +369,6 @@ describe('serverPatchReducer', () => {
undefined,
true,
],
overrideCanonicalUrl: undefined,
}

const newState = await serverPatchReducer(stateAfterNavigate, action)
Expand Down Expand Up @@ -573,29 +573,33 @@ describe('serverPatchReducer', () => {
</html>
)

const url = new URL('/linking/about', 'https://localhost') as any
const state = createInitialRouterState({
buildId,
initialTree,
initialHead: null,
initialCanonicalUrl,
initialSeedData: ['', {}, children],
initialParallelRoutes: new Map(),
location: new URL('/linking/about', 'https://localhost') as any,
location: url,
})

const action: ServerPatchAction = {
type: ACTION_SERVER_PATCH,
// this flight data is intentionally completely unrelated to the existing tree
flightData: [
serverResponse: [
[
'children',
'tree-patch-failure',
'children',
'new-page',
['new-page', { children: ['__PAGE__', {}] }],
null,
null,
[
'children',
'tree-patch-failure',
'children',
'new-page',
['new-page', { children: ['__PAGE__', {}] }],
null,
null,
],
],
undefined,
],
previousTree: [
'',
Expand All @@ -611,7 +615,6 @@ describe('serverPatchReducer', () => {
undefined,
true,
],
overrideCanonicalUrl: undefined,
}

const newState = await serverPatchReducer(state, action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export function serverPatchReducer(
state: ReadonlyReducerState,
action: ServerPatchAction
): ReducerState {
const { flightData, overrideCanonicalUrl } = action
const { serverResponse } = action
const [flightData, overrideCanonicalUrl] = serverResponse

const mutable: Mutable = {}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { CacheNode } from '../../../shared/lib/app-router-context.shared-runtime'
import type {
FlightRouterState,
FlightData,
FlightSegmentPath,
} from '../../../server/app-render/types'
import type { FetchServerResponseResult } from './fetch-server-response'
Expand All @@ -14,11 +13,13 @@ export const ACTION_PREFETCH = 'prefetch'
export const ACTION_FAST_REFRESH = 'fast-refresh'
export const ACTION_SERVER_ACTION = 'server-action'

export type RouterChangeByServerResponse = (
previousTree: FlightRouterState,
flightData: FlightData,
overrideCanonicalUrl: URL | undefined
) => void
export type RouterChangeByServerResponse = ({
previousTree,
serverResponse,
}: {
previousTree: FlightRouterState
serverResponse: FetchServerResponseResult
}) => void

export type RouterNavigate = (
href: string,
Expand Down Expand Up @@ -132,9 +133,8 @@ export interface RestoreAction {
*/
export interface ServerPatchAction {
type: typeof ACTION_SERVER_PATCH
flightData: FlightData
serverResponse: FetchServerResponseResult
previousTree: FlightRouterState
overrideCanonicalUrl: URL | undefined
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import type {
FocusAndScrollRef,
PrefetchKind,
RouterChangeByServerResponse,
} from '../../client/components/router-reducer/router-reducer-types'
import type { FetchServerResponseResult } from '../../client/components/router-reducer/fetch-server-response'
import type {
FlightRouterState,
FlightData,
} from '../../server/app-render/types'
import type { FlightRouterState } from '../../server/app-render/types'
import React from 'react'

export type ChildSegmentMap = Map<string, CacheNode>
Expand Down Expand Up @@ -144,11 +142,7 @@ export const LayoutRouterContext = React.createContext<{
export const GlobalLayoutRouterContext = React.createContext<{
buildId: string
tree: FlightRouterState
changeByServerResponse: (
previousTree: FlightRouterState,
flightData: FlightData,
overrideCanonicalUrl: URL | undefined
) => void
changeByServerResponse: RouterChangeByServerResponse
focusAndScrollRef: FocusAndScrollRef
nextUrl: string | null
}>(null as any)
Expand Down
Loading

0 comments on commit ef85241

Please sign in to comment.