Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/client/components/layout-router.tsx
- packages/next/src/client/components/app-router.tsx
- packages/next/src/server/app-render/create-component-tree.tsx
- packages/next/src/client/index.tsx
- packages/next/src/client/components/render-tree.ts
- packages/next/src/client/app-index.tsx
- packages/next/src/build/webpack/loaders/next-app-loader/index.ts
- packages/next/src/client/components/app-router-state.ts
- packages/next/src/client/components/app-router-instance.ts
- packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx
- packages/next/src/server/render.tsx
- packages/next/src/client/components/sequential-router-queue.ts
- packages/next/src/client/components/navigation.ts
- packages/next/src/shared/lib/app-router-context.shared-runtime.ts
- packages/next/src/client/components/links.ts
- packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts
- packages/next/src/client/components/app-router-announcer.tsx
- packages/next/src/client/link.tsx
- packages/next/src/client/resolve-href.ts
- packages/next/src/shared/lib/router/utils/interception-routes.ts
- packages/next/src/shared/lib/router/utils/is-dynamic.ts
- packages/next/src/shared/lib/router/utils/is-local-url.ts
- packages/next/src/shared/lib/utils.ts
Overview
The App Router coordinates client-side navigation, React Server Component (RSC) tree reconciliation, and route segment caching in Next.js applications. It processes client actions sequentially, handles link prefetching via intersection observation, and manages hierarchical layout nesting, error boundaries, and scroll restoration across route transitions.
Sources: packages/next/src/client/components/layout-router.tsx:33-40, packages/next/src/client/components/app-router.tsx:27-50, packages/next/src/client/components/app-router-state.ts:54-121, packages/next/src/client/components/links.ts:110-127
Client initialization and root provider hierarchy
Context Architecture and Root Providers
During browser bootstrapping, the client runtime consumes the initial Flight response and constructs the foundational component tree Client Runtime. The root hierarchy configures strict mode, head management, root layout boundaries, and screen reader route announcements through specialized context providers and portals.
Sources: packages/next/src/client/app-index.tsx:388-401, packages/next/src/client/app-index.tsx:280-311
The root provider hierarchy established in hydrate wraps the application in several nested contexts before mounting the App Rendering tree:
| Provider / Wrapper | Source File | Purpose |
|---|---|---|
StrictModeIfEnabled | packages/next/src/client/app-index.tsx | Conditionally enables React.StrictMode based on the __NEXT_STRICT_MODE_APP environment flag, falling back to React.Fragment. |
HeadManagerContext.Provider | packages/next/src/client/app-index.tsx | Configures document head management with appDir: true. |
Root | packages/next/src/client/app-index.tsx | Handles test mode hooks (window.__NEXT_HYDRATED) when __NEXT_TEST_MODE is active. |
MissingSlotContext | packages/next/src/shared/lib/app-router-context.shared-runtime.ts | Provides missing parallel route slot identifiers during development when initialRSCPayload.m is present. |
Sources: packages/next/src/client/app-index.tsx:313-328, packages/next/src/client/app-index.tsx:388-401, packages/next/src/shared/lib/app-router-context.shared-runtime.ts:124-124
Note
When document.documentElement.id equals __next_error__, server rendering has failed. In development, Next.js wraps the root element in RootLevelDevOverlayElement and invokes ReactDOMClient.createRoot directly without hydration to avoid mismatch errors.
Sources: packages/next/src/client/app-index.tsx:403-416
Accessibility Announcer and Shadow DOM
Screen reader route change announcements are managed by AppRouterAnnouncer. It creates or retrieves a custom element named next-route-announcer containing a shadow DOM with an assertive aria-live region (__next-route-announcer__).
function getAnnouncerNode() {
const existingAnnouncer = document.getElementsByName(ANNOUNCER_TYPE)[0]
if (existingAnnouncer?.shadowRoot?.childNodes[0]) {
return existingAnnouncer.shadowRoot.childNodes[0] as HTMLElement
} else {
const container = document.createElement(ANNOUNCER_TYPE)
container.style.cssText = 'position:absolute'
const announcer = document.createElement('div')
announcer.ariaLive = 'assertive'
announcer.id = ANNOUNCER_ID
announcer.role = 'alert'
announcer.style.cssText =
'position:absolute;border:0;height:1px;margin:-1px;padding:0;width:1px;clip:rect(0 0 0 0);overflow:hidden;white-space:nowrap;word-wrap:normal'
// Use shadow DOM here to avoid any potential CSS bleed
const shadow = container.attachShadow({ mode: 'open' })
shadow.appendChild(announcer)
document.body.appendChild(container)
return announcer
}
}Sources: packages/next/src/client/components/app-router-announcer.tsx:8-28
Upon route tree updates, the announcer evaluates document.title or falls back to the inner text of the first <h1> element. If the title changes after initial load, the new title string is ported into the shadow DOM container to notify assistive technologies.
useEffect(() => {
let currentTitle = ''
if (document.title) {
currentTitle = document.title
} else {
const pageHeader = document.querySelector('h1')
if (pageHeader) {
currentTitle = pageHeader.innerText || pageHeader.textContent || ''
}
}
// Only announce the title change, but not for the first load because screen
// readers do that automatically.
if (
previousTitle.current !== undefined &&
previousTitle.current !== currentTitle
) {
setRouteAnnouncement(currentTitle)
}
previousTitle.current = currentTitle
}, [tree])Sources: packages/next/src/client/components/app-router-announcer.tsx:47-67
Router action queue and navigation lifecycle
Sequential Action Execution and Transition Queuing
Navigation execution and state mutations in the App Router are coordinated through an action queue managed by AppRouterActionQueue nodes. When actions such as navigations, restores, or refreshes are dispatched via dispatchAction, they are either executed immediately if the queue is idle or appended to a linked list of ActionQueueNode items.
Sources: packages/next/src/client/components/app-router-instance.ts:30-39, packages/next/src/client/components/app-router-instance.ts:152-227
Navigations and back/forward traversals (ACTION_NAVIGATE and ACTION_RESTORE) hold high priority in the queue. When a navigation is dispatched while another action is pending, the current pending node is marked as discarded = true and actionQueue.wasPreempted is set to true so that intermediate states do not trigger unnecessary React renders.
| Action Type | Payload Structure | Execution Behavior | Source File |
|---|---|---|---|
ACTION_NAVIGATE | url, isExternalUrl, locationSearch, scrollBehavior, navigateType | Immediately preempts pending actions, marks pending nodes discarded, and kicks off sequential router transitions. | packages/next/src/client/components/sequential-router-queue.ts |
ACTION_RESTORE | url, historyState | Executes rapidly without creating deferred promises to prevent input latency during browser history popstate events. | packages/next/src/client/components/sequential-router-queue.ts |
ACTION_REFRESH | None | Flushes revalidated data when the queue returns to an idle state after discarded server actions. | packages/next/src/client/components/app-router-instance.ts |
ACTION_HMR_REFRESH | signal | Aborts superseded requests using an AbortController generation tracker in development mode. | packages/next/src/client/components/sequential-router-queue.ts |
Sources: packages/next/src/client/components/app-router-instance.ts:162-177, packages/next/src/client/components/sequential-router-queue.ts:17-25, packages/next/src/client/components/sequential-router-queue.ts:165-190
Note
ACTION_RESTORE actions bypass the creation of a deferred promise in dispatchAction. Because popstate events fire on browser back/forward traversals, avoiding asynchronous Promise wrappers ensures zero artificial delay on history navigations.
Sources: packages/next/src/client/components/app-router-instance.ts:162-166
External URL Detection and Navigation Runners
The sequential router queue enforces security guards before triggering navigations. If a target href resolves to a javascript: URL string, the action runner throws an immediate security error.
if (isJavaScriptURLString(href)) {
throw new Error(
'Next.js has blocked a javascript: URL as a security precaution.'
)
}Sources: packages/next/src/client/components/sequential-router-queue.ts:55-59
When a navigation is executed through navigate, transition wrappers add active transition types via React.addTransitionType and invoke startRouterTransition alongside dispatching the application router action with explicit external URL flags derived from isExternalURL(url).
if (transitionTypes) {
for (const type of transitionTypes) {
addTransitionType(type)
}
}
const url = new URL(addBasePath(href), location.href)
if (process.env.__NEXT_APP_NAV_FAIL_HANDLING) {
window.next.__pendingUrl = url
}
setLinkForCurrentNavigation(linkInstanceRef)
startRouterTransition(
href,
navigateType,
getRequiredAppRouterState().tree,
prefetchIntent
)
dispatchAppRouterAction({
type: ACTION_NAVIGATE,
url,
isExternalUrl: isExternalURL(url),
locationSearch: location.search,
scrollBehavior,
navigateType,
})
})Sources: packages/next/src/client/components/sequential-router-queue.ts:65-92
Caution
Discarded server actions that indicate data revalidation (didRevalidate: true) cause the action queue to flag actionQueue.needsRefresh = true. Once the queue drains completely, an automatic ACTION_REFRESH is dispatched to ensure server data consistency.
Sources: packages/next/src/client/components/app-router-instance.ts:114-123, packages/next/src/client/components/app-router-instance.ts:83-88
Client link resolution and destination checking
Link Resolution and Destination Check Execution
When an application renders a <Link> component, target URLs undergo a deterministic path normalization and resolution sequence before they can be evaluated as local routes or interception targets. The verified call chain for this check flows as follows: Link → resolveHref → isLocalURL → getLocationOrigin.
Link— The React component instance receiveshrefandasprops, memoizing their resolved string outputs viaresolveHrefwhen a router context is available. Sources: packages/next/src/client/link.tsx:476-491resolveHref— Formats string or object URLs, inspects for repeated slashes or backslashes, and passes the normalized string toisLocalURL. Sources: packages/next/src/client/resolve-href.ts:35-58isLocalURL— Determines whether the URL is routable by checking absolute scheme rules viaisAbsoluteUrland cross-origin matching viagetLocationOrigin. Sources: packages/next/src/shared/lib/router/utils/is-local-url.ts:7-17getLocationOrigin— Extractsprotocol,hostname, andportfromwindow.locationto construct the baseline origin string for local comparison. Sources: packages/next/src/shared/lib/utils.ts:338-341
Sources: packages/next/src/client/link.tsx:485-485, packages/next/src/client/resolve-href.ts:56-56, packages/next/src/shared/lib/router/utils/is-local-url.ts:9-12, packages/next/src/shared/lib/utils.ts:338-341
Absolute URL and Dynamic Route Interception Traces
Destination checking extends to validating absolute scheme headers and uncovering dynamic route segments that participate in route interception:
Link— InvokesresolveHrefto parse the target href for path interpolation. Sources: packages/next/src/client/link.tsx:485-485resolveHref— Evaluates dynamic routes usingisDynamicRouteand matches route parameters. Sources: packages/next/src/client/resolve-href.ts:70-74isLocalURL— CallsisAbsoluteUrlto execute a fast character code check againstABSOLUTE_URL_REGEX. Sources: packages/next/src/shared/lib/router/utils/is-local-url.ts:9-9, packages/next/src/shared/lib/utils.ts:327-336isAbsoluteUrl— Inspects whether the initial character code represents a letter and tests against/^[a-zA-Z][a-zA-Z\d+\-.]*?:/. Sources: packages/next/src/shared/lib/utils.ts:327-336
Sources: packages/next/src/client/link.tsx:485-485, packages/next/src/client/resolve-href.ts:105-105, packages/next/src/client/resolve-href.ts:56-56, packages/next/src/shared/lib/router/utils/is-local-url.ts:9-9, packages/next/src/shared/lib/utils.ts:327-336
Alternatively, dynamic route checks evaluate interception pathways:
Link— Resolves the destination href viaresolveHref. Sources: packages/next/src/client/link.tsx:485-485resolveHref— InvokesisDynamicRouteon path candidates. Sources: packages/next/src/client/resolve-href.ts:70-70isDynamicRoute— Inspects whether a route is dynamic and delegates toisInterceptionRouteAppPathif interception markers are detected. Sources: packages/next/src/shared/lib/router/utils/is-dynamic.ts:19-22isInterceptionRouteAppPath— Splits the path by/and checks segment matching againstINTERCEPTION_ROUTE_MARKERS. Sources: packages/next/src/shared/lib/router/utils/interception-routes.ts:119-128
Sources: packages/next/src/client/link.tsx:485-485, packages/next/src/client/resolve-href.ts:70-70, packages/next/src/shared/lib/router/utils/is-dynamic.ts:20-22, packages/next/src/shared/lib/router/utils/interception-routes.ts:119-128
Interception Markers and Route Utilities
Route interception relies on specific prefix markers defined in router utilities to intercept navigation targets across directory levels.
| Interception Marker | Behavior | Source File |
|---|---|---|
(..)(..) | Matches route segments two levels up from the intercepting route location. | packages/next/src/shared/lib/router/utils/interception-routes.ts:4-9 |
(.) | Matches sibling route segments at the same directory level as the intercepting route. | packages/next/src/shared/lib/router/utils/interception-routes.ts:4-9 |
(..) | Matches route segments one level up from the intercepting route location. | packages/next/src/shared/lib/router/utils/interception-routes.ts:4-9 |
(...) | Matches route segments located in the root directory. | packages/next/src/shared/lib/router/utils/interception-routes.ts:4-9 |
Sources: packages/next/src/shared/lib/router/utils/interception-routes.ts:4-9
Warning
Using the (..) interception marker at the root level (/) throws an explicit runtime error because root segments cannot ascend further. Developers must use the (.) marker for root-level sibling interceptions.
Sources: packages/next/src/shared/lib/router/utils/interception-routes.ts:179-183
Caution
Repeated forward slashes (//) or backslashes (\) inside an href string passed to resolveHref trigger a console error and are automatically normalized using normalizeRepeatedSlashes to prevent malformed route matching.
Sources: packages/next/src/client/resolve-href.ts:47-53
Visible link observation and prefetch coordination
Link instances coordinate with an IntersectionObserver instance initialized with a root margin of 200px to track visibility and trigger prefetch execution pipelines when links enter the viewport.
Sources: packages/next/src/client/components/links.ts:121-127
DOM elements are mapped to prefetchable instances using a WeakMap where available, or a standard Map fallback, while currently visible instances are maintained within a separate Set for efficient enumeration during cache invalidations or route changes.
Sources: packages/next/src/client/components/links.ts:110-119
export interface PrefetchOptions {
kind: PrefetchKind
onInvalidate?: () => void
}Sources: packages/next/src/shared/lib/app-router-context.shared-runtime.ts:30-33
Note
mountLinkInstance verifies whether prefetching is enabled and coerces destination paths into valid URL objects via coercePrefetchableUrl, reporting failures through reportError or console.error if string conversion fails.
Sources: packages/next/src/client/components/links.ts:144-182
Render tree construction and segment reconciliation
Navigating Prefetched Routes and Root Layouts
Client-side navigation checks the segment cache for matching fulfilled route entries, invoking navigateUsingPrefetchedRouteTree when an entry is found to construct the prefetch seed and initiate navigateToKnownRoute.
Sources: packages/next/src/client/components/app-router-state.ts:143-161, packages/next/src/client/components/app-router-state.ts:389-433
Navigations cross root layout boundaries when the highest-level layout changes between trees, requiring full-page multi-page application navigations. The isNavigatingToNewRootLayout function evaluates this condition by checking the PrefetchHint.IsRootLayoutOrAbove bit flag on both currentTree and nextTree.
Sources: packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts:1-26
Note
isNavigatingToNewRootLayout compares segment names and types while ignoring dynamic parameter values, allowing slugs like slug1 and slug2 under /[name]/layout.js to share a root layout unless group identifiers or structure change.
Sources: packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts:35-51
Reusing Inactive Segments and Creating Dynamic Requests
When soft navigations encounter reused default slots from older routes, reuseActiveSegmentInDefaultSlot rebases the inactive route tree and preserves the refresh context containing the canonical URL and rendered search state.
Sources: packages/next/src/client/components/render-tree.ts:939-971
The createDynamicRequestTree function constructs a FlightRouterState object instructing the server how to render requested segments, or returns null to skip requests when neither the segment nor its children require new data.
Sources: packages/next/src/client/components/render-tree.ts:880-914
Caution
The 'refetch' marker is explicitly assigned to index 3 of the top-most dynamic request tree node that requires new data, but is omitted if a parent segment was already marked.
Sources: packages/next/src/client/components/render-tree.ts:898-902
Layout boundary coordination and scroll handling
Hierarchical Layout Nesting and Error Boundaries
The client runtime manages hierarchical route structures through nested LayoutRouterContext and GlobalLayoutRouterContext providers. Each segment level accumulates route parameters and wrap segments within specialized runtime boundaries such as ErrorBoundary, RedirectBoundary, HTTPAccessFallbackBoundary, and instant validation components.
Sources: packages/next/src/client/components/layout-router.tsx:33-40, packages/next/src/client/components/layout-router.tsx:632-647, packages/next/src/shared/lib/app-router-context.shared-runtime.ts:97-113
| Context Provider | Value Structure | Purpose |
| :--- | :--- | :--- |
| `LayoutRouterContext` | `{ parentTree, parentRenderTree, parentSegmentPath, parentParams, parentLoadingData, debugNameContext, url, isActive }` | Provides current segment tree metadata, params, and active rendering state to nested child routers. |
| `GlobalLayoutRouterContext` | `{ tree, scrollRef, nextUrl, previousNextUrl }` | Exposes global navigation state and scroll handling references across layout trees. |
| `TemplateContext` | `React.ReactNode` | Wraps template leaf slots around page contents. |
| `AppRouterContext` | `AppRouterInstance | null` | Exposes imperative navigation methods (`push`, `replace`, `refresh`, `back`, `forward`, `prefetch`, `bfcacheId`) to components. |Sources: packages/next/src/client/components/layout-router.tsx:28-31, packages/next/src/shared/lib/app-router-context.shared-runtime.ts:35-96, packages/next/src/shared/lib/app-router-context.shared-runtime.ts:97-122
Note
useRouter() reads the bfcacheId numeric property from the closest parent render tree data node and formats it with a _b_ prefix and trailing underscores (e.g., _b_0_) to generate a unique string identifier for React keys.
Sources: packages/next/src/client/components/navigation.ts:179-192
Scroll Positioning and Viewport Target State
Scroll management is driven by InnerScrollHandler alongside helper utilities that evaluate target viewports and DOM nodes for hash fragments or element positions during route transitions.
Sources: packages/next/src/client/components/layout-router.tsx:55-137, packages/next/src/client/components/layout-router.tsx:148-160
The ScrollTargetState enum defines the viewport positioning classification returned during layout coordinate calculations.
| Enum Member | Meaning |
| :--- | :--- |
| `ScrollTargetState.NoClientRects` | The target instance returns zero client rectangles via `getClientRects()`. |
| `ScrollTargetState.InViewport` | The element top coordinate falls within the resolved scroll padding and viewport height. |
| `ScrollTargetState.OutOfViewport` | The element top coordinate lies outside the usable viewport bounds. |Sources: packages/next/src/client/components/layout-router.tsx:55-59, packages/next/src/client/components/layout-router.tsx:97-116
Warning
When a hash fragment equals 'top', getHashFragmentDomNode resolves directly to document.body; otherwise, it queries elements via document.getElementById or falls back to document.getElementsByName.
Sources: packages/next/src/client/components/layout-router.tsx:124-136
Related
On This Page
OverviewClient initialization and root provider hierarchyContext Architecture and Root ProvidersAccessibility Announcer and Shadow DOMRouter action queue and navigation lifecycleSequential Action Execution and Transition QueuingExternal URL Detection and Navigation RunnersClient link resolution and destination checkingLink Resolution and Destination Check ExecutionAbsolute URL and Dynamic Route Interception TracesInterception Markers and Route UtilitiesVisible link observation and prefetch coordinationRender tree construction and segment reconciliationNavigating Prefetched Routes and Root LayoutsReusing Inactive Segments and Creating Dynamic RequestsLayout boundary coordination and scroll handlingHierarchical Layout Nesting and Error BoundariesScroll Positioning and Viewport Target StateRelated