Relevant source files
The following files were used as context for generating this wiki page:
- packages/next-routing/src/resolve-routes.ts
- packages/next/src/shared/lib/router/router.ts
- packages/next/src/shared/lib/router/utils/prepare-destination.ts
- packages/next/src/server/lib/router-utils/resolve-routes.ts
- packages/next/src/client/route-params.ts
- packages/next/src/server/base-server.ts
- packages/next-routing/src/index.ts
- packages/next/src/client/route-loader.ts
- packages/next/src/client/resolve-href.ts
- packages/next/src/shared/lib/router/adapters.tsx
- packages/next/src/server/route-modules/route-module.ts
- packages/next/src/client/components/navigation.ts
- packages/next/src/server/lib/router-utils/filesystem.ts
- packages/next/src/client/components/segment-cache/cache.ts
- packages/next-routing/src/destination.ts
- packages/next/src/build/webpack/loaders/next-route-loader/index.ts
- packages/next/src/server/web/next-url.ts
- packages/next-routing/src/next-data.ts
- packages/next/src/server/server-utils.ts
- packages/next/src/client/components/router-transition.ts
- packages/next-routing/src/types.ts
- packages/eslint-plugin-next/src/utils/url.ts
- packages/next/src/client/components/navigation-dynamic-rendering.browser.ts
- packages/next-routing/package.json
Overview
Routing utilities orchestrate incoming HTTP request evaluation, path matching, middleware coordination, and destination compilation across Next.js routing layers. Provided primarily via @next/routing and core router subsystems, these utilities resolve route definitions, handle internationalization rules, normalize data URLs, and synchronize parameter extraction between client and server runtime components.
Sources: packages/next-routing/package.json:1-15, packages/next-routing/src/resolve-routes.ts:557-601
Public API Surface and Types
Public Export Contract
The @next/routing package exposes route resolution functionality, middleware helpers, internationalization detectors, and core TypeScript definitions through its primary entry point. The public export contract includes the resolveRoutes function alongside type definitions for route matching conditions (RouteHas), route rules (Route), middleware context and results (MiddlewareContext, MiddlewareResult), and resolution parameters and outcomes (ResolveRoutesParams, ResolveRoutesResult, ResolveRoutesQuery, RouteInvocationTarget). Additionally, internationalization utilities (detectLocale, detectDomainLocale, normalizeLocalePath, getAcceptLanguageLocale, getCookieLocale, I18nConfig, I18nDomain) and the middleware response mapper (responseToMiddlewareResult) are exported for external consumers.
export { resolveRoutes } from './resolve-routes'
export type {
RouteHas,
Route,
MiddlewareContext,
MiddlewareResult,
ResolveRoutesParams,
ResolveRoutesResult,
ResolveRoutesQuery,
ResolveRoutesQueryValue,
RouteInvocationTarget,
} from './types'
export type { I18nConfig, I18nDomain } from './i18n'
export {
detectLocale,
detectDomainLocale,
normalizeLocalePath,
getAcceptLanguageLocale,
getCookieLocale,
} from './i18n'
export { responseToMiddlewareResult } from './middleware'Sources: packages/next-routing/src/index.ts:1-21
RouteHas Conditions and Route Configurations
Conditional route matching relies on RouteHas union types, which distinguish between request headers, cookies, query parameters, and hostnames. A standard matcher configuration evaluates type fields against key and optional value constraints, whereas host-based matchers omit the key and enforce a strict value string match.
export type RouteHas =
| {
type: 'header' | 'cookie' | 'query'
key: string
value?: string
}
| {
type: 'host'
key?: undefined
value: string
}Sources: packages/next-routing/src/types.ts:1-11
Individual route entries (Route) define regex patterns, destination templates, response headers, status codes, and positive or negative match conditions (has and missing).
| Field | Type | Description |
|---|---|---|
sourceRegex | string | Regular expression string with named or unnamed capture matches |
destination | string (optional) | Replacement destination template using $1 or $name tokens |
headers | Record<string, string> (optional) | Custom headers to inject upon matching |
has | RouteHas[] (optional) | Required conditions that must be satisfied |
missing | RouteHas[] (optional) | Conditions that must not be present |
status | number (optional) | HTTP status code associated with the route |
Sources: packages/next-routing/src/types.ts:13-23
Invocation Types and Resolution Parameters
Parameters for route resolution (ResolveRoutesParams) aggregate request properties, build identifiers, base paths, internationalization settings, and categorical route arrays (beforeMiddleware, middlewareMatchers, beforeFiles, afterFiles, dynamicRoutes, onMatch, and fallback). Route matching defaults to case-insensitivity to mirror default Next.js behavior unless caseSensitive is explicitly enabled.
export type ResolveRoutesParams = {
url: URL
buildId: string
basePath: string
requestBody: ReadableStream
headers: Headers
pathnames: string[]
i18n?: {
defaultLocale: string
domains?: Array<{
defaultLocale: string
domain: string
http?: true
locales?: string[]
}>
localeDetection?: false
locales: string[]
}
routes: {
/**
* When false (default), route matching is case-insensitive to mirror
* Next.js default behavior. When true, matches are case-sensitive.
*/
caseSensitive?: boolean
beforeMiddleware: Array<Route>
/**
* Middleware matcher definitions used to decide whether middleware should
* be invoked for the current request.
*/
middlewareMatchers?: Array<Route>
beforeFiles: Array<Route>
afterFiles: Array<Route>
dynamicRoutes: Array<Route>
onMatch: Array<Route>
fallback: Array<Route>
shouldNormalizeNextData?: boolean
}
invokeMiddleware: (ctx: MiddlewareContext) => Promise<MiddlewareResult>
}Sources: packages/next-routing/src/types.ts:42-80
The outcome of the resolution pipeline is structured via ResolveRoutesResult, which encapsulates middleware responses, external rewrites, redirects, resolved pathnames, merged query parameters, and a concrete RouteInvocationTarget.
export type RouteInvocationTarget = {
/**
* Concrete pathname that should be invoked after routing resolution.
*/
pathname: string
/**
* Concrete query that should be invoked after routing resolution.
*/
query: ResolveRoutesQuery
}Sources: packages/next-routing/src/types.ts:85-94
Note
For dynamic routes, resolvedPathname returns the matched template pathname rather than the instantiated concrete URL path.
Sources: packages/next-routing/src/types.ts:103-107
Route Matching and Evaluation Pipeline
Request Evaluation Algorithm
Incoming requests flow through an ordered pipeline within resolveRoutes that handles URL initialization, internationalization prefixing, preprocessing via beforeMiddleware, middleware invocation, and localized redirects. Initial parameters such as headers, cookies, base paths, and build identifiers are unpacked, and initialUrl is copied into a mutable currentUrl instance for sequential transformations.
export async function resolveRoutes(
params: ResolveRoutesParams
): Promise<ResolveRoutesResult> {
const {
url: initialUrl,
basePath,
requestBody,
headers: initialHeaders,
pathnames,
routes,
invokeMiddleware,
buildId,
i18n,
} = params
const { shouldNormalizeNextData, caseSensitive = false } = routes
let currentUrl = new URL(initialUrl.toString())
let currentRequestHeaders = new Headers(initialHeaders)
let currentResponseHeaders = new Headers()
let currentStatus: number | undefined
let pendingLocaleRedirect:
| {
url: URL
status: number
}
| undefined
let pendingBeforeMiddlewareStop:
| {
status: number | undefined
}
| undefined
const initialOrigin = initialUrl.origin
// Check if the original URL is a data URL and normalize if so
let isDataUrl = false
if (shouldNormalizeNextData) {
const dataPrefix = `${basePath}/_next/data/${buildId}/`
isDataUrl = initialUrl.pathname.startsWith(dataPrefix)
if (isDataUrl) {
currentUrl = normalizeNextDataUrl(currentUrl, basePath, buildId)
}
}
Sources: packages/next-routing/src/resolve-routes.ts:557-601
If internationalization is configured and the route is not a Next.js data request or API path, the pipeline extracts locale information from the pathname or host headers. When locale auto-detection runs on index paths (/), a mismatched preferred locale triggers a pending redirect status of 307 to either a configured multi-domain target or a prefixed local path. Otherwise, the target locale is prefixed internally for subsequent route resolution phases without issuing an immediate client redirect.
// Handle i18n locale detection and redirects
if (i18n && !isDataUrl) {
const pathname = currentUrl.pathname.startsWith(basePath)
? currentUrl.pathname.slice(basePath.length) || '/'
: currentUrl.pathname
// Skip locale handling for _next and api routes
if (!pathname.startsWith('/_next/') && !pathname.startsWith('/api/')) {
const hostname = currentUrl.hostname
const cookieHeader = currentRequestHeaders.get('cookie') || undefined
const acceptLanguageHeader =
currentRequestHeaders.get('accept-language') || undefined
// Detect locale from path first
const pathLocaleResult = normalizeLocalePath(pathname, i18n.locales)
const localeInPath = !!pathLocaleResult.detectedLocale
// Detect domain locale
const domainLocale = detectDomainLocale(i18n.domains, hostname)
const defaultLocale = domainLocale?.defaultLocale || i18n.defaultLocale
// Determine target locale if locale detection is enabled
let targetLocale = pathLocaleResult.detectedLocale || defaultLocale
// Match Next.js behavior: preferred-locale auto-detection redirects only
// on index requests, not on arbitrary non-locale pathnames.
const shouldDetectPreferredLocale =
i18n.localeDetection !== false &&
!localeInPath &&
pathLocaleResult.pathname === '/'
if (shouldDetectPreferredLocale) {
const detectedResult = detectLocale({
pathname,
hostname,
cookieHeader,
acceptLanguageHeader,
i18n,
})
targetLocale = detectedResult.locale
// Check if we need to redirect based on domain or locale mismatch
if (targetLocale !== defaultLocale) {
const targetDomain = detectDomainLocale(
i18n.domains,
undefined,
targetLocale
)
// Redirect to different domain if target locale has a different configured domain
if (targetDomain && targetDomain.domain !== hostname) {
const scheme = targetDomain.http ? 'http' : 'https'
const localePrefix =
targetLocale === targetDomain.defaultLocale
? ''
: `/${targetLocale}`
const redirectUrl = new URL(
`${scheme}://${targetDomain.domain}${basePath}${localePrefix}${pathname}${currentUrl.search}`
)
pendingLocaleRedirect = {
url: redirectUrl,
status: 307,
}
}
// If no dedicated domain for target locale, or we're already on the right domain,
// redirect to add locale prefix on same domain
if (
!targetDomain ||
(targetDomain && targetDomain.domain === hostname)
) {
const redirectUrl = new URL(currentUrl.toString())
redirectUrl.pathname = `${basePath}/${targetLocale}${pathname}`
pendingLocaleRedirect = {
url: redirectUrl,
status: 307,
}Sources: packages/next-routing/src/resolve-routes.ts:602-693
Warning
Preferred-locale auto-detection only triggers redirect behavior on index requests where pathpathname equals '/', and explicitly skips arbitrary non-locale pathnames to prevent infinite redirection loops on deep links.
Sources: packages/next-routing/src/resolve-routes.ts:627-631
Middleware Matching and Invocation Checks
Before invoking middleware functions, requests pass through the beforeMiddleware route processor. If a redirect or external rewrite occurs during this phase, execution halts immediately and returns the resolved headers and status.
// Process beforeMiddleware routes
const beforeMiddlewareResult = processRoutes(
routes.beforeMiddleware,
currentUrl,
currentRequestHeaders,
currentResponseHeaders,
initialOrigin,
caseSensitive
)
if (beforeMiddlewareResult.status) {
currentStatus = beforeMiddlewareResult.status
}
if (beforeMiddlewareResult.redirect) {
return {
redirect: beforeMiddlewareResult.redirect,
resolvedHeaders: currentResponseHeaders,
status: currentStatus,
}
}
if (beforeMiddlewareResult.externalRewrite) {
return {
externalRewrite: beforeMiddlewareResult.externalRewrite,
resolvedHeaders: currentResponseHeaders,
status: currentStatus,
}
}
if (beforeMiddlewareResult.stopped) {
pendingBeforeMiddlewareStop = {
status: currentStatus,
}
}
currentUrl = beforeMiddlewareResult.url
Sources: packages/next-routing/src/resolve-routes.ts:695-732
If data URL normalization was active, URLs are denormalized before evaluating middleware matchers via shouldInvokeMiddlewareForRequest. When middleware runs, it can mutate request headers, append response set-cookie headers, or return redirect and rewrite destinations.
let middlewareInvocationUrl = currentUrl
// Denormalize before invoking middleware if this was originally a data URL
if (isDataUrl && shouldNormalizeNextData) {
middlewareInvocationUrl = denormalizeNextDataUrl(
currentUrl,
basePath,
buildId
)
}
const shouldInvokeMiddleware = shouldInvokeMiddlewareForRequest(
routes.middlewareMatchers,
currentUrl,
currentRequestHeaders,
caseSensitive
)
if (shouldInvokeMiddleware) {
// Invoke middleware
const middlewareResult = await invokeMiddleware({
url: middlewareInvocationUrl,
headers: currentRequestHeaders,
requestBody,
})
// Check if middleware sent the response body
if (middlewareResult.bodySent) {
return { middlewareResponded: true }
}
// Apply request headers from middleware
if (middlewareResult.requestHeaders) {
currentRequestHeaders = new Headers(middlewareResult.requestHeaders)
}
// Apply response headers from middleware
if (middlewareResult.responseHeaders) {
middlewareResult.responseHeaders.forEach((value, key) => {
if (key.toLowerCase() === 'set-cookie') {
currentResponseHeaders.append(key, value)
} else {
currentResponseHeaders.set(key, value)
}
})
}
// Handle middleware redirect
if (middlewareResult.redirect) {
if (!currentResponseHeaders.has('location')) {
currentResponseHeaders.set(
'Location',
middlewareResult.redirect.url.toString()
)
}
return {
resolvedHeaders: currentResponseHeaders,
status: middlewareResult.redirect.status,
}
}
// Handle middleware rewrite
if (middlewareResult.rewrite) {
currentUrl = middlewareResult.rewrite
// Check if it's an external rewrite
if (currentUrl.origin !== initialOrigin) {
return {
externalRewrite: currentUrl,
resolvedHeaders: currentResponseHeaders,
status: currentStatus,
}
}
}
}Sources: packages/next-routing/src/resolve-routes.ts:733-807
Note
Response headers returned by middleware are appended normally for standard keys, but set-cookie headers utilize .append() to preserve multiple cookie definitions instead of overwriting existing entries.
Sources: packages/next-routing/src/resolve-routes.ts:770-778
Destination Replacement and URL Construction
Dynamic Token Substitution and Placeholder Parsing
Destination string processing relies on token substitution mechanisms that map regular expression captures, named capture groups, and conditional has matches onto placeholder tokens. The replaceDestination utility extracts keys from hasCaptures and index-based or group-based regular expression matches, sorts them by length in descending order, and constructs an alternation regex pattern. Placeholder tokens matching digits are constrained with negative lookaheads to prevent partial numerical suffix collisions.
export function replaceDestination(
destination: string,
regexMatches: RegExpMatchArray | null,
hasCaptures: Record<string, string>
): string {
const captureKeys = Object.keys(hasCaptures)
if (regexMatches) {
for (let index = 1; index < regexMatches.length; index++) {
captureKeys.push(String(index))
}
if (regexMatches.groups) {
captureKeys.push(...Object.keys(regexMatches.groups))
}
}
if (captureKeys.length === 0) {
return destination
}
const capturePattern = captureKeys
.sort((first, second) => second.length - first.length)
.map((key) => {
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
return /^\d+$/.test(key) ? `${escapedKey}(?!\\d)` : escapedKey
})
.join('|')
// Replace placeholders once. Captured paths can contain literal text such as
// $2 or $d$segment that must not become another substitution.
return destination.replace(
new RegExp(`\\$(${capturePattern})`, 'g'),
(placeholder, key: string) => {
if (regexMatches) {
const index = Number(key)
if (
Number.isInteger(index) &&
index > 0 &&
index < regexMatches.length &&
String(index) === key
) {
return regexMatches[index] ?? ''
}
if (regexMatches.groups && Object.hasOwn(regexMatches.groups, key)) {
return regexMatches.groups[key] ?? ''
}
}
if (Object.hasOwn(hasCaptures, key)) {
return hasCaptures[key]
}
return placeholder
}
)
}Sources: packages/next-routing/src/destination.ts:5-58
Warning
Placeholder replacement runs exactly once to prevent security vulnerabilities or replacement loops where captured paths containing literal strings like $2 or $d$segment get re-evaluated as nested substitution tokens.
Sources: packages/next-routing/src/destination.ts:33-35
In more complex routing configurations, prepareDestination parses destination templates and parameter dictionaries through path-to-regexp compilers while disabling validation flags. Disabling compile validation prevents failures when merging named pattern parameters from a source route regex into an entirely separate destination template structure.
Sources: packages/next/src/shared/lib/router/utils/prepare-destination.ts:254-263
Query Merging and External Destination Handling
Once dynamic tokens are resolved, destination application handles URL mutation or absolute redirection based on protocol inspection. The isExternalDestination function checks whether a destination string begins with explicit protocol prefixes.
export function isExternalDestination(destination: string): boolean {
return destination.startsWith('http://') || destination.startsWith('https://')
}Sources: packages/next-routing/src/destination.ts:63-65
When applying internal or external destinations via applyDestination, the utility splits the destination string into pathname and search components. Query parameters parsed from the destination search string are merged into the target URL parameters using URLSearchParams iteration, updating existing keys or appending new values accordingly.
export function applyDestination(currentUrl: URL, destination: string): URL {
if (isExternalDestination(destination)) {
return new URL(destination)
}
// Create a new URL with the updated pathname
const newUrl = new URL(currentUrl.toString())
// Handle destinations with query strings
const [pathname, search] = destination.split('?')
newUrl.pathname = pathname
if (search) {
// Merge query parameters
const newParams = new URLSearchParams(search)
for (const [key, value] of newParams.entries()) {
newUrl.searchParams.set(key, value)
}
}
return newUrl
}Sources: packages/next-routing/src/destination.ts:71-92
Note
If a destination is identified as an external rewrite or redirect target via isExternalDestination, applyDestination bypasses query parameter merging on the current URL base and immediately constructs and returns a brand-new URL instance pointing to the external destination.
Sources: packages/next-routing/src/destination.ts:72-74
Next Data URL Normalization Cycle
Data URL Detection and Path Normalization
When Next.js routing handles requests, standalone data route resolution depends on normalizing incoming client requests that point to data route bundles. The routing lifecycle inspects whether data route normalization is enabled via shouldNormalizeNextData, identifying requests starting with the data prefix ${basePath}/_next/data/${buildId}/.
let isDataUrl = false
if (shouldNormalizeNextData) {
const dataPrefix = `${basePath}/_next/data/${buildId}/`
isDataUrl = initialUrl.pathname.startsWith(dataPrefix)
if (isDataUrl) {
currentUrl = normalizeNextDataUrl(currentUrl, basePath, buildId)
}
}Sources: packages/next-routing/src/resolve-routes.ts:592-600
When a request matches this structure, normalizeNextDataUrl strips the /_next/data/${buildId}/ segment and removes any trailing .json file extension from the path. If the resulting path equals index, it maps back to the root basePath or /. Otherwise, it reconstructs the standard pathname combining the base path and the remaining route segments.
export function normalizeNextDataUrl(
url: URL,
basePath: string,
buildId: string
): URL {
const newUrl = new URL(url.toString())
let pathname = newUrl.pathname
// Pattern: ${basePath}/_next/data/${buildId}/${path}.json
const dataPrefix = `${basePath}/_next/data/${buildId}/`
if (pathname.startsWith(dataPrefix)) {
// Remove the /_next/data/${buildId}/ part, keeping what comes after
let pathAfterData = pathname.slice(dataPrefix.length)
// Remove .json extension if present
if (pathAfterData.endsWith('.json')) {
pathAfterData = pathAfterData.slice(0, -5)
}
if (pathAfterData === 'index') {
pathname = basePath || '/'
} else {
pathname = basePath ? `${basePath}/${pathAfterData}` : `/${pathAfterData}`
}
newUrl.pathname = pathname
}
return newUrl
}Sources: packages/next-routing/src/next-data.ts:5-34
Note
During normalization, an empty or root-level index data request like /_next/data/build-id/index.json is explicitly transformed to match the root basePath rather than an empty string pathname.
Sources: packages/next-routing/src/next-data.ts:25-27
Denormalization and Re-attachment Cycle
Before middleware invocation, the routing cycle denormalizes the normalized URL back into its data route format so that middleware matchers and rewrite rules inspect the original data path structure.
// Denormalize before invoking middleware if this was originally a data URL
if (isDataUrl && shouldNormalizeNextData) {
middlewareInvocationUrl = denormalizeNextDataUrl(
currentUrl,
basePath,
buildId
)
}Sources: packages/next-routing/src/resolve-routes.ts:735-742
The denormalizeNextDataUrl function checks if the current URL lacks the data prefix. If absent, it strips the basePath, maps empty or root paths to /index, and re-attaches the /_next/data/${buildId}/ prefix alongside the .json extension.
export function denormalizeNextDataUrl(
url: URL,
basePath: string,
buildId: string
): URL {
const newUrl = new URL(url.toString())
let pathname = newUrl.pathname
// Only denormalize if it's not already a data URL
const dataPrefix = `${basePath}/_next/data/${buildId}/`
if (!pathname.startsWith(dataPrefix)) {
// Remove basePath if present
let pathWithoutBase = pathname
if (basePath && pathname.startsWith(basePath)) {
pathWithoutBase = pathname.slice(basePath.length)
}
if (pathWithoutBase === '' || pathWithoutBase === '/') {
pathWithoutBase = '/index'
}
// Add the /_next/data/${buildId}/ prefix and .json extension
pathname = `${basePath}/_next/data/${buildId}${pathWithoutBase}.json`
newUrl.pathname = pathname
}
return newUrlSources: packages/next-routing/src/next-data.ts:40-66
Warning
Denormalization execution is conditional: denormalizeNextDataUrl checks whether !pathname.startsWith(dataPrefix) to prevent recursive prefix duplication if a URL has already been transformed.
Sources: packages/next-routing/src/next-data.ts:49-50
Following middleware execution, if the request was originally flagged as a data URL, the routing engine normalizes the path a second time prior to evaluating beforeFiles route matching rules.
// Normalize again after middleware if this was originally a data URL
if (isDataUrl && shouldNormalizeNextData) {
currentUrl = normalizeNextDataUrl(currentUrl, basePath, buildId)
}Sources: packages/next-routing/src/resolve-routes.ts:829-832
Server and Client Routing Integration
Server Route Resolution and Filesystem Synchronization
Server-side routing operations coordinate request paths with concrete filesystem outputs using filesystem checkers and custom route tables. When resolveRoutes executes, it evaluates route execution flows sequentially against custom rewrites, redirects, before_files, middleware, and static filesystem assets via fsChecker.getItem(pathname).
const output = await fsChecker.getItem(pathname)Sources: packages/next/src/server/lib/router-utils/resolve-routes.ts:504-504
Note
Only page and app file outputs participate in route rendering. Public, static, image, and virtual filesystem items bypass template rendering and are served directly as static filesystem assets.
Sources: packages/next/src/server/lib/router-utils/filesystem.ts:845-849
Client-side navigation state bridges runtime parameter parsing through helper utilities like adaptForPathParams and adaptForSearchParams. These adapters translate the current browser router state into structured Params and URLSearchParams instances matching server expectations.
export function adaptForPathParams(
router: Pick<NextRouter, 'isReady' | 'pathname' | 'query' | 'asPath'>
): Params | null {
if (!router.isReady || !router.query) {
return null
}
const pathParams: Params = {}
const routeRegex = getRouteRegex(router.pathname)
const keys = Object.keys(routeRegex.groups)
for (const key of keys) {
pathParams[key] = router.query[key]!
}
return pathParams
}Sources: packages/next/src/shared/lib/router/adapters.tsx:57-70
URL Normalization and Canonical Parameter Decoding
Client-side href resolution handles parameter interpolation via resolveHref, normalizing paths and testing local URL validity before routing.
export function resolveHref(
router: NextRouter,
href: Url,
resolveAs: true
): [string, string] | [string]
export function resolveHref(
router: NextRouter,
href: Url,
resolveAs?: false
): string
export function resolveHref(
router: NextRouter,
href: Url,
resolveAs?: boolean
): [string, string] | [string] | string {Sources: packages/next/src/client/resolve-href.ts:18-32
To maintain consistent encodings between client URL parts and server-side route definitions, canonicalizeURLPart decodes incoming path segments and re-encodes them using encodeURIComponent. This prevents double-encoding issues for percent-encoded sequences like %2F.
export function canonicalizeURLPart(part: string): string {
try {
return encodeURIComponent(decodeURIComponent(part))
} catch {
// `decodeURIComponent` throws on malformed sequences. Fall back to the
// already-encoded form rather than failing the navigation.
return part
}
}Sources: packages/next/src/client/route-params.ts:86-94
Warning
The browser URL parser and server-side dynamic param decoders handle special characters differently; URL parsers leave characters like , and : untouched, whereas encodeURIComponent percent-encodes them.
Sources: packages/next/src/client/route-params.ts:78-85
Path and Search Param Synchronization
| Adapter / Function | Target Type | Input Source | Synchronization Behavior |
|---|---|---|---|
adaptForSearchParams | URLSearchParams | router.asPath | Extracts query parameters from the active browser path string. |
adaptForPathParams | Params | router.query | Extracts dynamic route parameters keyed by route regex group names. |
canonicalizeURLPart | string | URL pathname part | Decodes and re-encodes path segments to match server-side parameters. |
getRenderedPathname | NormalizedPathname | RSCResponse | Resolves pathnames using server headers or response URLs. |
Sources: packages/next/src/shared/lib/router/adapters.tsx:47-70, packages/next/src/client/route-params.ts:48-63, packages/next/src/client/route-params.ts:86-94
Related
On This Page
OverviewPublic API Surface and TypesPublic Export ContractRouteHas Conditions and Route ConfigurationsInvocation Types and Resolution ParametersRoute Matching and Evaluation PipelineRequest Evaluation AlgorithmMiddleware Matching and Invocation ChecksDestination Replacement and URL ConstructionDynamic Token Substitution and Placeholder ParsingQuery Merging and External Destination HandlingNext Data URL Normalization CycleData URL Detection and Path NormalizationDenormalization and Re-attachment CycleServer and Client Routing IntegrationServer Route Resolution and Filesystem SynchronizationURL Normalization and Canonical Parameter DecodingPath and Search Param SynchronizationRelated