Open-Source Wikis

/

Next.js

/

Systems

/

App Router

vercel/next.js

App Router

The React Server Components-based router rooted at the app/ directory in user code. Introduced in March 2023, the App Router is the framework's default for new projects and the focus of most ongoing investment. It supports streaming, server actions, partial prerendering (PPR), and the cache components primitive.

Two halves

graph LR
    subgraph Server
        AppRender["app-render.tsx<br/>~7,000 lines"]
        EntryBase["entry-base.ts<br/>RSDW boundary"]
        ActionHandler["action-handler.ts<br/>50KB"]
        WorkStorage["work-async-storage.external.ts"]
    end

    subgraph Client
        AppRouter["app-router.tsx"]
        LayoutRouter["layout-router.tsx"]
        Reducer["router-reducer/"]
        SegmentCache["segment-cache/"]
    end

    User[Browser] -- HTTP --> AppRender
    AppRender -- RSC stream --> User
    User -- prefetch / nav --> AppRouter
    AppRouter --> Reducer
    Reducer --> SegmentCache

Server side

app-render.tsx

packages/next/src/server/app-render/app-render.tsx is the entry point — the largest single file in the codebase at 8,759 lines. It does:

  1. Read the work async storage to populate per-request context (URL, params, cookies, headers).
  2. Build the loader tree from the route match (the segment hierarchy: layouts, pages, error boundaries, loading boundaries).
  3. Render the React Server Components tree, encoding it as a Flight payload via react-server-dom-webpack/server (or react-server-dom-turbopack/server for Turbopack builds).
  4. Pipe the SSR HTML output, interleaving Flight chunks for streaming.
  5. If PPR is enabled, postpone-track dynamic boundaries and serialize the postponed state.
  6. Run the after-task queue (unstable_after).

The function deliberately interleaves all of these because React's RSC + SSR + postpone APIs need a single render cycle.

entry-base.ts

packages/next/src/server/app-render/entry-base.ts is the only file in the framework allowed to import from react-server-dom-webpack/*. Every other file accesses these via re-exports here. The reason: Turbopack swaps the import to react-server-dom-turbopack at module-resolution time, and a single boundary file makes that swap surgical.

Loader tree

The "loader tree" is the data structure that mirrors the App Router segment hierarchy. Each node has a layout, optionally a page, error boundaries, loading boundaries, and templates. Helpers:

  • packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx — walks the loader tree applying the client's router state.
  • packages/next/src/server/app-render/create-component-tree.tsx (47 KB) — turns loader tree segments into actual React component trees.
  • packages/next/src/server/app-render/create-flight-router-state-from-loader-tree.ts — produces the router state used by the client.

Async storages used during a render

Storage Holds
work-async-storage.external.ts Per-request work context (URL, params, etc.)
work-unit-async-storage.external.ts Per-render-unit context (cache scopes)
action-async-storage.external.ts Active server action invocation
after-task-async-storage.external.ts Queued unstable_after tasks
console-async-storage.external.ts Console capture for replay
dynamic-access-async-storage.external.ts Dynamic API access tracking for PPR

PPR (Partial Prerendering)

packages/next/src/server/app-render/postponed-state.ts and dynamic-rendering.ts (50 KB) handle the postponed-state machinery. When a render encounters a dynamic API (e.g., headers(), cookies(), searchParams), the React tree at that point is "postponed" — the static prefix is committed and the dynamic suffix is deferred until request time.

When __NEXT_CACHE_COMPONENTS=true is set, most app-dir pages enable PPR implicitly. The legacy ppr-full/ and ppr/ test suites are mostly describe.skip while the migration to cache components completes.

Client side

app-router.tsx

packages/next/src/client/components/app-router.tsx (23 KB) is the React Provider that wraps the app-dir tree on the client. It sets up:

  • The router instance (app-router-instance.ts)
  • The async-action queue (use-action-queue.ts)
  • The error boundary (error-boundary.tsx)
  • The redirect boundary (redirect-boundary.tsx)
  • The <RouteAnnouncer> for accessibility

layout-router.tsx

layout-router.tsx (31 KB) is the per-segment component that renders the appropriate layout, page, error boundary, or loading boundary based on the router state. It lazily fetches Flight payloads for new segments via the segment cache.

router-reducer

packages/next/src/client/components/router-reducer/ is the state machine for navigation. The big file is ppr-navigations.ts (2,061 lines) which handles PPR-aware navigation: when a navigation lands on a postponed page, the client must replay the postponed state with the new dynamic data.

Segment cache

packages/next/src/client/components/segment-cache/cache.ts (3,112 lines) is the client-side cache of fetched segments. It deduplicates prefetches, handles BFCache state via bfcache-state-manager.ts, and reconciles cache entries on navigation.

packages/next/src/client/components/links.ts is the client implementation backing <Link>. It implements:

  • IntersectionObserver-based prefetch on viewport entry.
  • Prefetch on hover (configurable).
  • Click handling that translates to a router push.
  • BFCache-aware prefetch invalidation.

The next/link server component lives at packages/next/src/client/link.tsx.

Streaming integration

The stream-ops files at packages/next/src/server/app-render/stream-ops.{ts,node.ts,web.ts} provide the per-runtime stream stitching:

  • Node runtime (stream-ops.node.ts, 32 KB) — uses Node's stream piping to interleave Flight chunks and HTML.
  • Web runtime (stream-ops.web.ts) — uses Web Streams (ReadableStream / TransformStream).
  • The shared file stream-ops.ts re-exports the right one based on the runtime.

Server actions integration

Server actions are App-Router-only. The handler at packages/next/src/server/app-render/action-handler.ts (51 KB) processes form posts and direct calls. See server actions.

Integration points

  • Imports from react-server-dom-webpack/* via entry-base.ts only.
  • Reads the loader tree built by packages/next/src/build/route-discovery.ts.
  • Reads page manifests from .next/app-build-manifest.json etc.
  • Writes Flight payloads consumed by the client app-router.

Entry points for modification

  • To change a server-side render behavior: packages/next/src/server/app-render/app-render.tsx or one of its helpers.
  • To add a new dynamic API: define it in packages/next/src/server/request/, register tracking in dynamic-rendering.ts.
  • To change client navigation: packages/next/src/client/components/router-reducer/.
  • To change segment caching: packages/next/src/client/components/segment-cache/cache.ts.

Key source files

File Purpose
packages/next/src/server/app-render/app-render.tsx Entry render function
packages/next/src/server/app-render/entry-base.ts RSDW import boundary
packages/next/src/server/app-render/create-component-tree.tsx Loader-tree → React tree
packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx Apply client router state to tree
packages/next/src/server/app-render/dynamic-rendering.ts Dynamic API tracking + PPR postpone
packages/next/src/server/app-render/postponed-state.ts PPR postponed-state serialization
packages/next/src/server/app-render/stream-ops.node.ts Node streaming pipe
packages/next/src/client/components/app-router.tsx Client provider
packages/next/src/client/components/layout-router.tsx Per-segment client renderer
packages/next/src/client/components/router-reducer/ppr-navigations.ts PPR-aware navigation state
packages/next/src/client/components/segment-cache/cache.ts Client segment cache
packages/next/src/client/components/links.ts <Link> client implementation

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

App Router – Next.js wiki | Factory