Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,8 @@ function Router({
// Root node always has `url`
// Provided in AppTreeContext to ensure it can be overwritten in layout-router
url: canonicalUrl,
// Root segment is always active
isActive: true,
}
}, [tree, cache, canonicalUrl])

Expand Down
81 changes: 45 additions & 36 deletions packages/next/src/client/components/layout-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,13 @@ function InnerLayoutRouter({
segmentPath,
cacheNode,
url,
isActive,
}: {
tree: FlightRouterState
segmentPath: FlightSegmentPath
cacheNode: CacheNode
url: string
isActive: boolean
}) {
const context = useContext(GlobalLayoutRouterContext)
const parentNavPromises = useContext(NavigationPromisesContext)
Expand Down Expand Up @@ -382,44 +384,48 @@ function InnerLayoutRouter({
// navigation that will be able to fulfill it. We need to fetch more from
// the server and patch the cache.

// Check if there's already a pending request.
let lazyData = cacheNode.lazyData
if (lazyData === null) {
/**
* Router state with refetch marker added
*/
// TODO-APP: remove ''
const refetchTree = walkAddRefetch(['', ...segmentPath], fullTree)
const includeNextUrl = hasInterceptionRouteInCurrentTree(fullTree)
const navigatedAt = Date.now()
cacheNode.lazyData = lazyData = fetchServerResponse(
new URL(url, location.origin),
{
flightRouterState: refetchTree,
nextUrl: includeNextUrl
? // We always send the last next-url, not the current when
// performing a dynamic request. This is because we update
// the next-url after a navigation, but we want the same
// interception route to be matched that used the last
// next-url.
context.previousNextUrl || context.nextUrl
: null,
}
).then((serverResponse) => {
startTransition(() => {
dispatchAppRouterAction({
type: ACTION_SERVER_PATCH,
previousTree: fullTree,
serverResponse,
navigatedAt,
// Only fetch data for the active segment. Inactive segments (rendered
// offscreen for bfcache) should not trigger fetches.
if (isActive) {
// Check if there's already a pending request.
let lazyData = cacheNode.lazyData
if (lazyData === null) {
/**
* Router state with refetch marker added
*/
// TODO-APP: remove ''
const refetchTree = walkAddRefetch(['', ...segmentPath], fullTree)
const includeNextUrl = hasInterceptionRouteInCurrentTree(fullTree)
const navigatedAt = Date.now()
cacheNode.lazyData = lazyData = fetchServerResponse(
new URL(url, location.origin),
{
flightRouterState: refetchTree,
nextUrl: includeNextUrl
? // We always send the last next-url, not the current when
// performing a dynamic request. This is because we update
// the next-url after a navigation, but we want the same
// interception route to be matched that used the last
// next-url.
context.previousNextUrl || context.nextUrl
: null,
}
).then((serverResponse) => {
startTransition(() => {
dispatchAppRouterAction({
type: ACTION_SERVER_PATCH,
previousTree: fullTree,
serverResponse,
navigatedAt,
})
})
})

return serverResponse
})
return serverResponse
})

// Suspend while waiting for lazyData to resolve
use(lazyData)
// Suspend while waiting for lazyData to resolve
use(lazyData)
}
}
// Suspend infinitely as `changeByServerResponse` will cause a different part of the tree to be rendered.
// A falsey `resolvedRsc` indicates missing data -- we should not commit that branch, and we need to wait for the data to arrive.
Expand Down Expand Up @@ -461,6 +467,7 @@ function InnerLayoutRouter({

// TODO-APP: overriding of url for parallel routes
url: url,
isActive: isActive,
}}
>
{content}
Expand Down Expand Up @@ -558,7 +565,8 @@ export default function OuterLayoutRouter({
throw new Error('invariant expected layout router to be mounted')
}

const { parentTree, parentCacheNode, parentSegmentPath, url } = context
const { parentTree, parentCacheNode, parentSegmentPath, url, isActive } =
context

// Get the CacheNode for this segment by reading it from the parent segment's
// child map.
Expand Down Expand Up @@ -691,6 +699,7 @@ export default function OuterLayoutRouter({
tree={tree}
cacheNode={cacheNode}
segmentPath={segmentPath}
isActive={isActive && stateKey === activeStateKey}
/>
{segmentBoundaryTriggerNode}
</RedirectBoundary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const LayoutRouterContext = React.createContext<{
parentCacheNode: CacheNode
parentSegmentPath: FlightSegmentPath | null
url: string
isActive: boolean
} | null>(null)

export const GlobalLayoutRouterContext = React.createContext<{
Expand Down
6 changes: 3 additions & 3 deletions test/development/acceptance-app/hydration-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ describe('Error overlay for hydration errors in App router', () => {
<HTTPAccessFallbackBoundary notFound={undefined} forbidden={undefined} unauthorized={undefined}>
<RedirectBoundary>
<RedirectErrorBoundary router={{...}}>
<InnerLayoutRouter url="/extra-nod..." tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
<InnerLayoutRouter url="/extra-nod..." tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]} ...>
<SegmentViewNode type="page" pagePath="(default)/...">
<SegmentTrieNode>
<ClientPageRoot Component={function Mismatch} searchParams={{}} params={{}}>
Expand Down Expand Up @@ -514,7 +514,7 @@ describe('Error overlay for hydration errors in App router', () => {
<HTTPAccessFallbackBoundary notFound={undefined} forbidden={undefined} unauthorized={undefined}>
<RedirectBoundary>
<RedirectErrorBoundary router={{...}}>
<InnerLayoutRouter url="/p-under-p" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
<InnerLayoutRouter url="/p-under-p" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]} ...>
<SegmentViewNode type="page" pagePath="(default)/...">
<SegmentTrieNode>
<ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
Expand Down Expand Up @@ -570,7 +570,7 @@ describe('Error overlay for hydration errors in App router', () => {
<HTTPAccessFallbackBoundary notFound={undefined} forbidden={undefined} unauthorized={undefined}>
<RedirectBoundary>
<RedirectErrorBoundary router={{...}}>
<InnerLayoutRouter url="/div-under-p" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
<InnerLayoutRouter url="/div-under-p" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]} ...>
<SegmentViewNode type="page" pagePath="(default)/...">
<SegmentTrieNode>
<ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('hydration-error-count', () => {
<HTTPAccessFallbackBoundary notFound={undefined} forbidden={undefined} unauthorized={undefined}>
<RedirectBoundary>
<RedirectErrorBoundary router={{...}}>
<InnerLayoutRouter url="/html-diff" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
<InnerLayoutRouter url="/html-diff" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]} ...>
<SegmentViewNode type="page" pagePath="html-diff/...">
<SegmentTrieNode>
<ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
Expand Down
Loading