Skip to content
Closed
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
4 changes: 2 additions & 2 deletions packages/next/src/build/static-paths/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import path from 'node:path'
import { AfterRunner } from '../../server/after/run-with-after'
import { createWorkStore } from '../../server/async-storage/work-store'
import { FallbackMode } from '../../lib/fallback'
import { getRouteMatcher } from '../../shared/lib/router/utils/route-matcher'
import { getRouteParamKeys } from '../../shared/lib/router/utils/route-param-keys'
import {
getRouteRegex,
type RouteRegex,
Expand Down Expand Up @@ -471,7 +471,7 @@ export async function buildAppStaticPaths({
})

const regex = getRouteRegex(page)
const routeParamKeys = Object.keys(getRouteMatcher(regex)(page) || {})
const routeParamKeys = getRouteParamKeys(regex.groups)

const afterRunner = new AfterRunner()

Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/build/static-paths/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { removeTrailingSlash } from '../../shared/lib/router/utils/remove-traili
import { getRouteMatcher } from '../../shared/lib/router/utils/route-matcher'
import { getRouteRegex } from '../../shared/lib/router/utils/route-regex'
import { encodeParam, normalizePathname } from './utils'
import { getRouteParamKeys } from '../../shared/lib/router/utils/route-param-keys'

export async function buildPagesStaticPaths({
page,
Expand All @@ -27,7 +28,7 @@ export async function buildPagesStaticPaths({
const _routeMatcher = getRouteMatcher(_routeRegex)

// Get the default list of allowed params.
const routeParameterKeys = Object.keys(_routeMatcher(page))
const routeParameterKeys = getRouteParamKeys(_routeRegex.groups)
const staticPathsResult = await getStaticPaths({
// We create a copy here to avoid having the types of `getStaticPaths`
// change. This ensures that users can't mutate this array and have it
Expand Down
5 changes: 4 additions & 1 deletion packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-pa
import * as Log from '../build/output/log'
import { getPreviouslyRevalidatedTags, getServerUtils } from './server-utils'
import isError, { getProperError } from '../lib/is-error'
import { getRouteParamKeys } from '../shared/lib/router/utils/route-param-keys'
import {
addRequestMeta,
getRequestMeta,
Expand Down Expand Up @@ -1396,7 +1397,9 @@ export default abstract class Server<
if (pageIsDynamic || didRewrite) {
utils.normalizeCdnUrl(req, [
...rewriteParamKeys,
...Object.keys(utils.defaultRouteRegex?.groups || {}),
...(utils.defaultRouteRegex
? getRouteParamKeys(utils.defaultRouteRegex.groups)
: []),
])
}
// Remove the route `params` keys from `parsedUrl.query` if they are
Expand Down
13 changes: 3 additions & 10 deletions packages/next/src/server/request/fallback-params.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { getRouteMatcher } from '../../shared/lib/router/utils/route-matcher'
import { getRouteParamKeys } from '../../shared/lib/router/utils/route-param-keys'
import { getRouteRegex } from '../../shared/lib/router/utils/route-regex'

export type FallbackRouteParams = ReadonlyMap<string, string>

function getParamKeys(page: string) {
const pattern = getRouteRegex(page)
const matcher = getRouteMatcher(pattern)

// Get the default list of allowed params.
return Object.keys(matcher(page))
}

export function getFallbackRouteParams(
pageOrKeys: string | readonly string[]
): FallbackRouteParams | null {
let keys: readonly string[]
if (typeof pageOrKeys === 'string') {
keys = getParamKeys(pageOrKeys)
const { groups } = getRouteRegex(pageOrKeys)
keys = getRouteParamKeys(groups)
} else {
keys = pageOrKeys
}
Expand Down
11 changes: 6 additions & 5 deletions packages/next/src/server/server-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ import { parseAndValidateFlightRouterState } from './app-render/parse-and-valida
import { isInterceptionRouteRewrite } from '../lib/generate-interception-routes-rewrites'
import { NEXT_ROUTER_STATE_TREE_HEADER } from '../client/components/app-router-headers'
import { getSelectedParams } from '../client/components/router-reducer/compute-changed-path'
import { getRouteParamKeys } from '../shared/lib/router/utils/route-param-keys'

function filterInternalQuery(
query: Record<string, undefined | string | string[]>,
paramKeys: string[]
paramKeys: readonly string[]
) {
// this is used to pass query information in rewrites
// but should not be exposed in final query
Expand All @@ -60,7 +61,7 @@ function filterInternalQuery(

export function normalizeCdnUrl(
req: BaseNextRequest | IncomingMessage,
paramKeys: string[]
paramKeys: readonly string[]
) {
// make sure to normalize req.url from CDNs to strip dynamic and rewrite
// params from the query which are added during routing
Expand All @@ -83,7 +84,7 @@ export function interpolateDynamicPath(
) {
if (!defaultRouteRegex) return pathname

for (const param of Object.keys(defaultRouteRegex.groups)) {
for (const param of getRouteParamKeys(defaultRouteRegex.groups)) {
const { optional, repeat } = defaultRouteRegex.groups[param]
let builtParam = `[${repeat ? '...' : ''}${param}]`

Expand Down Expand Up @@ -119,7 +120,7 @@ export function normalizeDynamicRouteParams(
let hasValidParams = true
let params: ParsedUrlQuery = {}

for (const key of Object.keys(defaultRouteRegex.groups)) {
for (const key of getRouteParamKeys(defaultRouteRegex.groups)) {
let value: string | string[] | undefined = query[key]

if (typeof value === 'string') {
Expand Down Expand Up @@ -396,7 +397,7 @@ export function getServerUtils({

// Use all the named route keys.
const result = {} as RegExpExecArray
for (const keyName of Object.keys(routeKeys)) {
for (const keyName of getRouteParamKeys(groups)) {
const paramName = routeKeys[keyName]

// If this param name is not a valid parameter name, then skip it.
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/server/web-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getEdgeInstrumentationModule } from './web/globals'
import type { ServerOnInstrumentationRequestError } from './app-render/types'
import { getEdgePreviewProps } from './web/get-edge-preview-props'
import { NoFallbackError } from '../shared/lib/no-fallback-error.external'
import { getRouteParamKeys } from '../shared/lib/router/utils/route-param-keys'

interface WebServerOptions extends Options {
buildId: string
Expand Down Expand Up @@ -186,7 +187,7 @@ export default class NextWebServer extends BaseServer<
normalizedParams,
routeRegex
)
normalizeCdnUrl(req, Object.keys(routeRegex.routeKeys))
normalizeCdnUrl(req, getRouteParamKeys(routeRegex.groups))
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/shared/lib/router/adapters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PathnameContext } from '../hooks-client-context.shared-runtime'
import { isDynamicRoute } from './utils'
import { asPathToSearchParams } from './utils/as-path-to-search-params'
import { getRouteRegex } from './utils/route-regex'
import { getRouteParamKeys } from './utils/route-param-keys'

/** It adapts a Pages Router (`NextRouter`) to the App Router Instance. */
export function adaptForAppRouterInstance(
Expand Down Expand Up @@ -59,7 +60,7 @@ export function adaptForPathParams(
}
const pathParams: Params = {}
const routeRegex = getRouteRegex(router.pathname)
const keys = Object.keys(routeRegex.groups)
const keys = getRouteParamKeys(routeRegex.groups)
for (const key of keys) {
pathParams[key] = router.query[key]!
}
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/shared/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { isDynamicRoute } from './utils/is-dynamic'
import { parseRelativeUrl } from './utils/parse-relative-url'
import { getRouteMatcher } from './utils/route-matcher'
import { getRouteRegex } from './utils/route-regex'
import { getRouteParamKeys } from './utils/route-param-keys'
import { formatWithValidation } from './utils/format-url'
import { detectDomainLocale } from '../../../client/detect-domain-locale'
import { parsePath } from './utils/parse-path'
Expand Down Expand Up @@ -1504,7 +1505,7 @@ export default class Router implements BaseRouter {
: ({} as { result: undefined; params: undefined })

if (!routeMatch || (shouldInterpolate && !interpolatedAs.result)) {
const missingParams = Object.keys(routeRegex.groups).filter(
const missingParams = getRouteParamKeys(routeRegex.groups).filter(
(param) => !query[param] && !routeRegex.groups[param].optional
)

Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/shared/lib/router/utils/interpolate-as.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ParsedUrlQuery } from 'querystring'

import { getRouteMatcher } from './route-matcher'
import { getRouteRegex } from './route-regex'
import { getRouteParamKeys } from './route-param-keys'

export function interpolateAs(
route: string,
Expand All @@ -11,7 +12,6 @@ export function interpolateAs(
let interpolatedRoute = ''

const dynamicRegex = getRouteRegex(route)
const dynamicGroups = dynamicRegex.groups
const dynamicMatches =
// Try to match the dynamic route against the asPath
(asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : '') ||
Expand All @@ -20,12 +20,12 @@ export function interpolateAs(
query

interpolatedRoute = route
const params = Object.keys(dynamicGroups)
const params = getRouteParamKeys(dynamicRegex.groups)

if (
!params.every((param) => {
let value = dynamicMatches[param] || ''
const { repeat, optional } = dynamicGroups[param]
const { repeat, optional } = dynamicRegex.groups[param]

// support single-level catch-all
// TODO: more robust handling for user-error (passing `/`)
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/shared/lib/router/utils/omit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export function omit<T extends { [key: string]: unknown }, K extends keyof T>(
object: T,
keys: K[]
keys: readonly K[]
): Omit<T, K> {
const omitted: { [key: string]: unknown } = {}
Object.keys(object).forEach((key) => {
Expand Down
22 changes: 22 additions & 0 deletions packages/next/src/shared/lib/router/utils/route-param-keys.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getRouteRegex } from './route-regex'
import { getRouteParamKeys } from './route-param-keys'

describe('getRouteParamKeys', () => {
it('should return the correct param keys', () => {
const { groups } = getRouteRegex('/[...slug].json')
expect(getRouteParamKeys(groups)).toEqual(['slug'])
})

it('should have the correct ordering', () => {
const { groups } = getRouteRegex('/[lang]/[...slug]')
expect(getRouteParamKeys(groups)).toEqual(['lang', 'slug'])
})

it('should have the correct ordering when the groups object is not sorted', () => {
const groups = {
slug: { pos: 2, repeat: true, optional: false },
lang: { pos: 1, repeat: false, optional: false },
}
expect(getRouteParamKeys(groups)).toEqual(['lang', 'slug'])
})
})
19 changes: 19 additions & 0 deletions packages/next/src/shared/lib/router/utils/route-param-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Group } from './route-regex'

/**
* Get the parameter keys from a route regex, sorted by their position in the
* route regex which corresponds to the order they appear in the route.
*
* @param groups - The groups of the route regex.
* @returns The parameter keys in the order they appear in the route regex.
*/
export function getRouteParamKeys(
groups: Record<string, Group>
): readonly string[] {
const keys = Object.keys(groups)

// Sort keys directly by their position values
keys.sort((a, b) => groups[a].pos - groups[b].pos)

return keys
}
Loading