Skip to main content
doc0blade47/next.jsdocumentation home
Sign in

App Router

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

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 / WrapperSource FilePurpose
StrictModeIfEnabledpackages/next/src/client/app-index.tsxConditionally enables React.StrictMode based on the __NEXT_STRICT_MODE_APP environment flag, falling back to React.Fragment.
HeadManagerContext.Providerpackages/next/src/client/app-index.tsxConfigures document head management with appDir: true.
Rootpackages/next/src/client/app-index.tsxHandles test mode hooks (window.__NEXT_HYDRATED) when __NEXT_TEST_MODE is active.
MissingSlotContextpackages/next/src/shared/lib/app-router-context.shared-runtime.tsProvides 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 TypePayload StructureExecution BehaviorSource File
ACTION_NAVIGATEurl, isExternalUrl, locationSearch, scrollBehavior, navigateTypeImmediately preempts pending actions, marks pending nodes discarded, and kicks off sequential router transitions.packages/next/src/client/components/sequential-router-queue.ts
ACTION_RESTOREurl, historyStateExecutes rapidly without creating deferred promises to prevent input latency during browser history popstate events.packages/next/src/client/components/sequential-router-queue.ts
ACTION_REFRESHNoneFlushes 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_REFRESHsignalAborts 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

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.

  1. Link — The React component instance receives href and as props, memoizing their resolved string outputs via resolveHref when a router context is available. Sources: packages/next/src/client/link.tsx:476-491
  2. resolveHref — Formats string or object URLs, inspects for repeated slashes or backslashes, and passes the normalized string to isLocalURL. Sources: packages/next/src/client/resolve-href.ts:35-58
  3. isLocalURL — Determines whether the URL is routable by checking absolute scheme rules via isAbsoluteUrl and cross-origin matching via getLocationOrigin. Sources: packages/next/src/shared/lib/router/utils/is-local-url.ts:7-17
  4. getLocationOrigin — Extracts protocol, hostname, and port from window.location to 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:

  1. Link — Invokes resolveHref to parse the target href for path interpolation. Sources: packages/next/src/client/link.tsx:485-485
  2. resolveHref — Evaluates dynamic routes using isDynamicRoute and matches route parameters. Sources: packages/next/src/client/resolve-href.ts:70-74
  3. isLocalURL — Calls isAbsoluteUrl to execute a fast character code check against ABSOLUTE_URL_REGEX. Sources: packages/next/src/shared/lib/router/utils/is-local-url.ts:9-9, packages/next/src/shared/lib/utils.ts:327-336
  4. isAbsoluteUrl — 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:

  1. Link — Resolves the destination href via resolveHref. Sources: packages/next/src/client/link.tsx:485-485
  2. resolveHref — Invokes isDynamicRoute on path candidates. Sources: packages/next/src/client/resolve-href.ts:70-70
  3. isDynamicRoute — Inspects whether a route is dynamic and delegates to isInterceptionRouteAppPath if interception markers are detected. Sources: packages/next/src/shared/lib/router/utils/is-dynamic.ts:19-22
  4. isInterceptionRouteAppPath — Splits the path by / and checks segment matching against INTERCEPTION_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 MarkerBehaviorSource 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

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

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

Was this page helpful?