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
34 changes: 13 additions & 21 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ import {
import { getStartServerInfo, logStartInfo } from '../server/lib/app-info-log'
import type { NextEnabledDirectories } from '../server/base-server'
import { hasCustomExportOutput } from '../export/utils'
import { isInterceptionRouteAppPath } from '../server/lib/interception-routes'
import {
getTurbopackJsConfig,
handleEntrypoints,
Expand Down Expand Up @@ -2106,8 +2105,6 @@ export default async function build(
}

const appConfig = workerResult.appConfig || {}
const isInterceptionRoute =
isInterceptionRouteAppPath(page)
if (appConfig.revalidate !== 0) {
const isDynamic = isDynamicRoute(page)
const hasGenerateStaticParams =
Expand All @@ -2124,27 +2121,22 @@ export default async function build(
}

// Mark the app as static if:
// - It's not an interception route (these currently depend on request headers and cannot be computed at build)
// - It has no dynamic param
// - It doesn't have generateStaticParams but `dynamic` is set to
// `error` or `force-static`
if (!isInterceptionRoute) {
if (!isDynamic) {
appStaticPaths.set(originalAppPath, [page])
appStaticPathsEncoded.set(originalAppPath, [
page,
])
isStatic = true
} else if (
!hasGenerateStaticParams &&
(appConfig.dynamic === 'error' ||
appConfig.dynamic === 'force-static')
) {
appStaticPaths.set(originalAppPath, [])
appStaticPathsEncoded.set(originalAppPath, [])
isStatic = true
isRoutePPREnabled = false
}
if (!isDynamic) {
appStaticPaths.set(originalAppPath, [page])
appStaticPathsEncoded.set(originalAppPath, [page])
isStatic = true
} else if (
!hasGenerateStaticParams &&
(appConfig.dynamic === 'error' ||
appConfig.dynamic === 'force-static')
) {
appStaticPaths.set(originalAppPath, [])
appStaticPathsEncoded.set(originalAppPath, [])
isStatic = true
isRoutePPREnabled = false
}
}

Expand Down
33 changes: 1 addition & 32 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ import { unresolvedThenable } from './unresolved-thenable'
import { NEXT_RSC_UNION_QUERY } from './app-router-headers'
import { removeBasePath } from '../remove-base-path'
import { hasBasePath } from '../has-base-path'
import { PAGE_SEGMENT_KEY } from '../../shared/lib/segment'
import type { Params } from '../../shared/lib/router/utils/route-matcher'
import { getSelectedParams } from './router-reducer/compute-changed-path'
import type { FlightRouterState } from '../../server/app-render/types'
const isServer = typeof window === 'undefined'

Expand Down Expand Up @@ -98,36 +97,6 @@ export function urlToUrlWithoutFlightMarker(url: string): URL {
return urlWithoutFlightParameters
}

// this function performs a depth-first search of the tree to find the selected
// params
function getSelectedParams(
currentTree: FlightRouterState,
params: Params = {}
): Params {
const parallelRoutes = currentTree[1]

for (const parallelRoute of Object.values(parallelRoutes)) {
const segment = parallelRoute[0]
const isDynamicParameter = Array.isArray(segment)
const segmentValue = isDynamicParameter ? segment[1] : segment
if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) continue

// Ensure catchAll and optional catchall are turned into an array
const isCatchAll =
isDynamicParameter && (segment[2] === 'c' || segment[2] === 'oc')

if (isCatchAll) {
params[segment[0]] = segment[1].split('/')
} else if (isDynamicParameter) {
params[segment[0]] = segment[1]
}

params = getSelectedParams(parallelRoute, params)
}

return params
}

type AppRouterProps = Omit<
Omit<InitialRouterStateParameters, 'isServer' | 'location'>,
'initialParallelRoutes'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
Segment,
} from '../../../server/app-render/types'
import { INTERCEPTION_ROUTE_MARKERS } from '../../../server/lib/interception-routes'
import type { Params } from '../../../shared/lib/router/utils/route-matcher'
import {
isGroupSegment,
DEFAULT_SEGMENT_KEY,
Expand Down Expand Up @@ -130,3 +131,34 @@ export function computeChangedPath(
// lightweight normalization to remove route groups
return normalizeSegments(changedPath.split('/'))
}

/**
* Recursively extracts dynamic parameters from FlightRouterState.
*/
export function getSelectedParams(
currentTree: FlightRouterState,
params: Params = {}
): Params {
const parallelRoutes = currentTree[1]

for (const parallelRoute of Object.values(parallelRoutes)) {
const segment = parallelRoute[0]
const isDynamicParameter = Array.isArray(segment)
const segmentValue = isDynamicParameter ? segment[1] : segment
if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) continue

// Ensure catchAll and optional catchall are turned into an array
const isCatchAll =
isDynamicParameter && (segment[2] === 'c' || segment[2] === 'oc')

if (isCatchAll) {
params[segment[0]] = segment[1].split('/')
} else if (isDynamicParameter) {
params[segment[0]] = segment[1]
}

params = getSelectedParams(parallelRoute, params)
}

return params
}
30 changes: 1 addition & 29 deletions packages/next/src/lib/generate-interception-routes-rewrites.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp'
import { NEXT_URL } from '../client/components/app-router-headers'
import {
INTERCEPTION_ROUTE_MARKERS,
extractInterceptionRouteInformation,
isInterceptionRouteAppPath,
} from '../server/lib/interception-routes'
Expand All @@ -21,31 +20,6 @@ function toPathToRegexpPath(path: string): string {
})
}

// for interception routes we don't have access to the dynamic segments from the
// referrer route so we mark them as noop for the app renderer so that it
// can retrieve them from the router state later on. This also allows us to
// compile the route properly with path-to-regexp, otherwise it will throw
function voidParamsBeforeInterceptionMarker(path: string): string {
let newPath = []

let foundInterceptionMarker = false
for (const segment of path.split('/')) {
if (
INTERCEPTION_ROUTE_MARKERS.find((marker) => segment.startsWith(marker))
) {
foundInterceptionMarker = true
}

if (segment.startsWith(':') && !foundInterceptionMarker) {
newPath.push('__NEXT_EMPTY_PARAM__')
} else {
newPath.push(segment)
}
}

return newPath.join('/')
}

export function generateInterceptionRoutesRewrites(
appPaths: string[],
basePath = ''
Expand All @@ -62,9 +36,7 @@ export function generateInterceptionRoutesRewrites(
}/(.*)?`

const normalizedInterceptedRoute = toPathToRegexpPath(interceptedRoute)
const normalizedAppPath = voidParamsBeforeInterceptionMarker(
toPathToRegexpPath(appPath)
)
const normalizedAppPath = toPathToRegexpPath(appPath)

// pathToRegexp returns a regex that matches the path, but we need to
// convert it to a string that can be used in a header value
Expand Down
75 changes: 4 additions & 71 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import {
continueDynamicHTMLResume,
continueDynamicDataResume,
} from '../stream-utils/node-web-streams-helper'
import { canSegmentBeOverridden } from '../../client/components/match-segments'
import { stripInternalQueries } from '../internal-utils'
import {
NEXT_ROUTER_PREFETCH_HEADER,
Expand Down Expand Up @@ -103,7 +102,6 @@ import {
StaticGenBailoutError,
isStaticGenBailoutError,
} from '../../client/components/static-generation-bailout'
import { isInterceptionRouteAppPath } from '../lib/interception-routes'
import { getStackWithoutErrorMessage } from '../../lib/format-server-error'
import {
usedDynamicAPIs,
Expand Down Expand Up @@ -162,63 +160,14 @@ function createNotFoundLoaderTree(loaderTree: LoaderTree): LoaderTree {
return ['', {}, loaderTree[2]]
}

/* This method is important for intercepted routes to function:
* when a route is intercepted, e.g. /blog/[slug], it will be rendered
* with the layout of the previous page, e.g. /profile/[id]. The problem is
* that the loader tree needs to know the dynamic param in order to render (id and slug in the example).
* Normally they are read from the path but since we are intercepting the route, the path would not contain id,
* so we need to read it from the router state.
*/
function findDynamicParamFromRouterState(
flightRouterState: FlightRouterState | undefined,
segment: string
): {
param: string
value: string | string[] | null
treeSegment: Segment
type: DynamicParamTypesShort
} | null {
if (!flightRouterState) {
return null
}

const treeSegment = flightRouterState[0]

if (canSegmentBeOverridden(segment, treeSegment)) {
if (!Array.isArray(treeSegment) || Array.isArray(segment)) {
return null
}

return {
param: treeSegment[0],
value: treeSegment[1],
treeSegment: treeSegment,
type: treeSegment[2],
}
}

for (const parallelRouterState of Object.values(flightRouterState[1])) {
const maybeDynamicParam = findDynamicParamFromRouterState(
parallelRouterState,
segment
)
if (maybeDynamicParam) {
return maybeDynamicParam
}
}

return null
}

export type CreateSegmentPath = (child: FlightSegmentPath) => FlightSegmentPath

/**
* Returns a function that parses the dynamic segment and return the associated value.
*/
function makeGetDynamicParamFromSegment(
params: { [key: string]: any },
pagePath: string,
flightRouterState: FlightRouterState | undefined
pagePath: string
): GetDynamicParamFromSegment {
return function getDynamicParamFromSegment(
// [slug] / [[slug]] / [...slug]
Expand All @@ -233,11 +182,6 @@ function makeGetDynamicParamFromSegment(

let value = params[key]

// this is a special marker that will be present for interception routes
if (value === '__NEXT_EMPTY_PARAM__') {
value = undefined
}

if (Array.isArray(value)) {
value = value.map((i) => encodeURIComponent(i))
} else if (typeof value === 'string') {
Expand Down Expand Up @@ -283,8 +227,6 @@ function makeGetDynamicParamFromSegment(
treeSegment: [key, value.join('/'), dynamicParamType],
}
}

return findDynamicParamFromRouterState(flightRouterState, segment)
}

const type = getShortDynamicParamType(segmentParam.type)
Expand Down Expand Up @@ -820,16 +762,10 @@ async function renderToHTMLOrFlightImpl(
* Router state provided from the client-side router. Used to handle rendering
* from the common layout down. This value will be undefined if the request
* is not a client-side navigation request or if the request is a prefetch
* request (except when it's a prefetch request for an interception route
* which is always dynamic).
* request.
*/
const shouldProvideFlightRouterState =
isRSCRequest &&
(!isPrefetchRSCRequest ||
!isRoutePPREnabled ||
// Interception routes currently depend on the flight router state to
// extract dynamic params.
isInterceptionRouteAppPath(pagePath))
isRSCRequest && (!isPrefetchRSCRequest || !isRoutePPREnabled)

const parsedFlightRouterState = parseAndValidateFlightRouterState(
req.headers[NEXT_ROUTER_STATE_TREE.toLowerCase()]
Expand All @@ -854,10 +790,7 @@ async function renderToHTMLOrFlightImpl(

const getDynamicParamFromSegment = makeGetDynamicParamFromSegment(
params,
pagePath,
// `FlightRouterState` is unconditionally provided here because this method uses it
// to extract dynamic params as a fallback if they're not present in the path.
parsedFlightRouterState
pagePath
)

// Get the nonce from the incoming request if it has one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import type { FlightRouterState } from './types'
import { flightRouterStateSchema } from './types'
import { assert } from 'next/dist/compiled/superstruct'

export function parseAndValidateFlightRouterState(
stateHeader: string | string[]
): FlightRouterState
export function parseAndValidateFlightRouterState(
stateHeader: undefined
): undefined
export function parseAndValidateFlightRouterState(
stateHeader: string | string[] | undefined
): FlightRouterState | undefined
export function parseAndValidateFlightRouterState(
stateHeader: string | string[] | undefined
): FlightRouterState | undefined {
Expand Down
Loading