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
1 change: 1 addition & 0 deletions packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export function hydrate(
navigatedAt: initialTimestamp,
initialFlightData: initialRSCPayload.f,
initialCanonicalUrlParts: initialRSCPayload.c,
initialRenderedSearch: initialRSCPayload.q,
initialParallelRoutes: new Map(),
location: window.location,
}),
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/client/components/app-router-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type NavigateAction,
ACTION_HMR_REFRESH,
PrefetchKind,
type AppHistoryState,
} from './router-reducer/router-reducer-types'
import { reducer } from './router-reducer/router-reducer'
import { startTransition } from 'react'
Expand All @@ -27,7 +28,6 @@ import type {
PrefetchOptions,
} from '../../shared/lib/app-router-context.shared-runtime'
import { setLinkForCurrentNavigation, type LinkInstance } from './links'
import type { FlightRouterState } from '../../shared/lib/app-router-types'
import type { ClientInstrumentationHooks } from '../app-index'
import type { GlobalErrorComponent } from './builtin/global-error'

Expand Down Expand Up @@ -305,7 +305,7 @@ export function dispatchNavigateAction(

export function dispatchTraverseAction(
href: string,
tree: FlightRouterState | undefined
historyState: AppHistoryState | undefined
) {
const onRouterTransitionStart = getProfilingHookForOnNavigationStart()
if (onRouterTransitionStart !== null) {
Expand All @@ -314,7 +314,7 @@ export function dispatchTraverseAction(
dispatchAppRouterAction({
type: ACTION_RESTORE,
url: new URL(href),
tree,
historyState,
})
}

Expand Down
26 changes: 16 additions & 10 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
LayoutRouterContext,
GlobalLayoutRouterContext,
} from '../../shared/lib/app-router-context.shared-runtime'
import type {
CacheNode,
FlightRouterState,
} from '../../shared/lib/app-router-types'
import type { CacheNode } from '../../shared/lib/app-router-types'
import { ACTION_RESTORE } from './router-reducer/router-reducer-types'
import type { AppRouterState } from './router-reducer/router-reducer-types'
import type {
AppHistoryState,
AppRouterState,
} from './router-reducer/router-reducer-types'
import { createHrefFromUrl } from './router-reducer/create-href-from-url'
import {
SearchParamsContext,
Expand Down Expand Up @@ -61,14 +61,20 @@ function HistoryUpdater({
window.next.__pendingUrl = undefined
}

const { tree, pushRef, canonicalUrl } = appRouterState
const { tree, pushRef, canonicalUrl, renderedSearch } = appRouterState

const appHistoryState: AppHistoryState = {
tree,
renderedSearch,
}

const historyState = {
...(pushRef.preserveCustomHistoryState ? window.history.state : {}),
// Identifier is shortened intentionally.
// __NA is used to identify if the history entry can be handled by the app-router.
// __N is used to identify if the history entry can be handled by the old router.
__NA: true,
__PRIVATE_NEXTJS_INTERNALS_TREE: tree,
__PRIVATE_NEXTJS_INTERNALS_TREE: appHistoryState,
}
if (
pushRef.pendingPush &&
Expand Down Expand Up @@ -217,7 +223,7 @@ function Router({
dispatchAppRouterAction({
type: ACTION_RESTORE,
url: new URL(window.location.href),
tree: window.history.state.__PRIVATE_NEXTJS_INTERNALS_TREE,
historyState: window.history.state.__PRIVATE_NEXTJS_INTERNALS_TREE,
})
}

Expand Down Expand Up @@ -300,14 +306,14 @@ function Router({
url: string | URL | null | undefined
) => {
const href = window.location.href
const tree: FlightRouterState | undefined =
const appHistoryState: AppHistoryState | undefined =
window.history.state?.__PRIVATE_NEXTJS_INTERNALS_TREE

startTransition(() => {
dispatchAppRouterAction({
type: ACTION_RESTORE,
url: new URL(url ?? href, href),
tree,
historyState: appHistoryState,
})
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function handleAliasedPrefetchEntry(
state: ReadonlyReducerState,
flightData: string | NormalizedFlightData[],
url: URL,
renderedSearch: string,
mutable: Mutable
) {
let currentTree = state.tree
Expand Down Expand Up @@ -140,6 +141,7 @@ export function handleAliasedPrefetchEntry(
}

mutable.patchedTree = currentTree
mutable.renderedSearch = renderedSearch
mutable.cache = currentCache
mutable.canonicalUrl = href
mutable.hashFragment = url.hash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('createInitialRouterState', () => {
navigatedAt,
initialFlightData: [[initialTree, ['', children, {}, null]]],
initialCanonicalUrlParts: initialCanonicalUrl.split('/'),
initialRenderedSearch: '',
initialParallelRoutes,
location: new URL('/linking', 'https://localhost') as any,
})
Expand All @@ -46,6 +47,7 @@ describe('createInitialRouterState', () => {
navigatedAt,
initialFlightData: [[initialTree, ['', children, {}, null]]],
initialCanonicalUrlParts: initialCanonicalUrl.split('/'),
initialRenderedSearch: '',
initialParallelRoutes,
location: new URL('/linking', 'https://localhost') as any,
})
Expand Down Expand Up @@ -102,6 +104,7 @@ describe('createInitialRouterState', () => {
const expected: ReturnType<typeof createInitialRouterState> = {
tree: initialTree,
canonicalUrl: initialCanonicalUrl,
renderedSearch: '',
pushRef: {
pendingPush: false,
mpaNavigation: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import type {
import { createHrefFromUrl } from './create-href-from-url'
import { fillLazyItemsTillLeafWithHead } from './fill-lazy-items-till-leaf-with-head'
import { extractPathFromFlightRouterState } from './compute-changed-path'

import type { AppRouterState } from './router-reducer-types'
import { addRefreshMarkerToActiveParallelSegments } from './refetch-inactive-parallel-segments'
import { getFlightDataPartsFromPath } from '../../flight-data-helpers'

export interface InitialRouterStateParameters {
navigatedAt: number
initialCanonicalUrlParts: string[]
initialRenderedSearch: string
initialParallelRoutes: CacheNode['parallelRoutes']
initialFlightData: FlightDataPath[]
location: Location | null
Expand All @@ -21,9 +24,10 @@ export function createInitialRouterState({
navigatedAt,
initialFlightData,
initialCanonicalUrlParts,
initialRenderedSearch,
initialParallelRoutes,
location,
}: InitialRouterStateParameters) {
}: InitialRouterStateParameters): AppRouterState {
// When initialized on the server, the canonical URL is provided as an array of parts.
// This is to ensure that when the RSC payload streamed to the client, crawlers don't interpret it
// as a URL that should be crawled.
Expand Down Expand Up @@ -91,6 +95,7 @@ export function createInitialRouterState({
segmentPaths: [],
},
canonicalUrl,
renderedSearch: initialRenderedSearch,
nextUrl:
// the || operator is intentional, the pathname can be an empty string
(extractPathFromFlightRouterState(initialTree) || location?.pathname) ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ import {
} from '../../flight-data-helpers'
import { getAppBuildId } from '../../app-build-id'
import { setCacheBustingSearchParam } from './set-cache-busting-search-param'
import { urlToUrlWithoutFlightMarker } from '../../route-params'
import {
getRenderedSearch,
urlToUrlWithoutFlightMarker,
} from '../../route-params'
import type { NormalizedSearch } from '../segment-cache'

const createFromReadableStream =
createFromReadableStreamBrowser as (typeof import('react-server-dom-webpack/client.browser'))['createFromReadableStream']
Expand All @@ -63,16 +67,23 @@ export interface FetchServerResponseOptions {
readonly isHmrRefresh?: boolean
}

export type FetchServerResponseResult = {
flightData: NormalizedFlightData[] | string
canonicalUrl: URL | undefined
type SpaFetchServerResponseResult = {
flightData: NormalizedFlightData[]
canonicalUrl: URL
renderedSearch: NormalizedSearch
couldBeIntercepted: boolean
prerendered: boolean
postponed: boolean
staleTime: number
debugInfo: Array<any> | null
}

type MpaFetchServerResponseResult = string

export type FetchServerResponseResult =
| MpaFetchServerResponseResult
| SpaFetchServerResponseResult

export type RequestHeaders = {
[RSC_HEADER]?: '1'
[NEXT_ROUTER_STATE_TREE_HEADER]?: string
Expand All @@ -88,17 +99,7 @@ export type RequestHeaders = {
}

function doMpaNavigation(url: string): FetchServerResponseResult {
return {
flightData: urlToUrlWithoutFlightMarker(
new URL(url, location.origin)
).toString(),
canonicalUrl: undefined,
couldBeIntercepted: false,
prerendered: false,
postponed: false,
staleTime: -1,
debugInfo: null,
}
return urlToUrlWithoutFlightMarker(new URL(url, location.origin)).toString()
}

let abortController = new AbortController()
Expand Down Expand Up @@ -197,7 +198,7 @@ export async function fetchServerResponse(
)

const responseUrl = urlToUrlWithoutFlightMarker(new URL(res.url))
const canonicalUrl = res.redirected ? responseUrl : undefined
const canonicalUrl = res.redirected ? responseUrl : url

const contentType = res.headers.get('content-type') || ''
const interception = !!res.headers.get('vary')?.includes(NEXT_URL)
Expand Down Expand Up @@ -266,9 +267,15 @@ export async function fetchServerResponse(
return doMpaNavigation(res.url)
}

const normalizedFlightData = normalizeFlightData(flightResponse.f)
if (typeof normalizedFlightData === 'string') {
return doMpaNavigation(normalizedFlightData)
}

return {
flightData: normalizeFlightData(flightResponse.f),
flightData: normalizedFlightData,
canonicalUrl: canonicalUrl,
renderedSearch: getRenderedSearch(res),
couldBeIntercepted: interception,
prerendered: flightResponse.S,
postponed,
Expand All @@ -286,15 +293,7 @@ export async function fetchServerResponse(
// If fetch fails handle it like a mpa navigation
// TODO-APP: Add a test for the case where a CORS request fails, e.g. external url redirect coming from the response.
// See https://github.com/vercel/next.js/issues/43605#issuecomment-1451617521 for a reproduction.
return {
flightData: url.toString(),
canonicalUrl: undefined,
couldBeIntercepted: false,
prerendered: false,
postponed: false,
staleTime: -1,
debugInfo: null,
}
return url.toString()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,8 @@ export function handleMutable(

return {
// Set href.
canonicalUrl: isNotUndefined(mutable.canonicalUrl)
? mutable.canonicalUrl === state.canonicalUrl
? state.canonicalUrl
: mutable.canonicalUrl
: state.canonicalUrl,
canonicalUrl: mutable.canonicalUrl ?? state.canonicalUrl,
renderedSearch: mutable.renderedSearch ?? state.renderedSearch,
pushRef: {
pendingPush: isNotUndefined(mutable.pendingPush)
? mutable.pendingPush
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -790,13 +790,14 @@ export function listenForDynamicRequest(
responsePromise: Promise<FetchServerResponseResult>
) {
responsePromise.then(
({ flightData, debugInfo }: FetchServerResponseResult) => {
if (typeof flightData === 'string') {
(result: FetchServerResponseResult) => {
if (typeof result === 'string') {
// Happens when navigating to page in `pages` from `app`. We shouldn't
// get here because should have already handled this during
// the prefetch.
return
}
const { flightData, debugInfo } = result
for (const normalizedFlightData of flightData) {
const {
segmentPath,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { fetchServerResponse } from '../fetch-server-response'
import {
fetchServerResponse,
type FetchServerResponseResult,
} from '../fetch-server-response'
import { createHrefFromUrl } from '../create-href-from-url'
import { applyRouterStatePatchToTree } from '../apply-router-state-patch-to-tree'
import { isNavigatingToNewRootLayout } from '../is-navigating-to-new-root-layout'
Expand Down Expand Up @@ -42,17 +45,19 @@ function hmrRefreshReducerImpl(
})

return cache.lazyData.then(
({ flightData, canonicalUrl: canonicalUrlOverride }) => {
(result: FetchServerResponseResult) => {
// Handle case when navigating to page in `pages` from `app`
if (typeof flightData === 'string') {
if (typeof result === 'string') {
return handleExternalUrl(
state,
mutable,
flightData,
result,
state.pushRef.pendingPush
)
}

const { flightData, canonicalUrl, renderedSearch } = result

// Remove cache.lazyData as it has been resolved at this point.
cache.lazyData = null

Expand Down Expand Up @@ -88,13 +93,6 @@ function hmrRefreshReducerImpl(
)
}

const canonicalUrlOverrideHref = canonicalUrlOverride
? createHrefFromUrl(canonicalUrlOverride)
: undefined

if (canonicalUrlOverride) {
mutable.canonicalUrl = canonicalUrlOverrideHref
}
const applied = applyFlightData(
navigatedAt,
currentCache,
Expand All @@ -108,7 +106,8 @@ function hmrRefreshReducerImpl(
}

mutable.patchedTree = newTree
mutable.canonicalUrl = href
mutable.renderedSearch = renderedSearch
mutable.canonicalUrl = createHrefFromUrl(canonicalUrl)

currentTree = newTree
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ function handleNavigationResult(
// Received a new result.
mutable.cache = result.data.cacheNode
mutable.patchedTree = result.data.flightRouterState
mutable.renderedSearch = result.data.renderedSearch
mutable.canonicalUrl = result.data.canonicalUrl
mutable.scrollableSegments = result.data.scrollableSegments
mutable.shouldScroll = result.data.shouldScroll
Expand Down
Loading
Loading