---
title: "Dev Server and HMR"
description: "The Next.js development server and Hot Module Replacement (HMR) subsystem bridges local source files with browser-side execution, handling compilation orchestration, dynamic route resolution, and r..."
last_updated: "2026-09-23T10:52:03.186406+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development-and-diagnostics/dev-server-and-hmr"
---

<details>
<summary>Relevant source files</summary>

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

- [packages/next/src/server/dev/hot-reloader-webpack.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-webpack.ts)
- [packages/next/src/server/dev/hot-reloader-turbopack.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-turbopack.ts)
- [packages/next/src/server/dev/next-dev-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/next-dev-server.ts)
- [packages/next/src/server/dev/on-demand-entry-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts)
- [packages/next/src/cli/next-dev.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/cli/next-dev.ts)
- [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx)
- [packages/next/src/server/lib/router-utils/setup-dev-bundler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts)
- [packages/next/src/server/dev/hot-middleware.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-middleware.ts)
- [packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts)
- [packages/next/src/server/lib/dev-bundler-service.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/dev-bundler-service.ts)
- [packages/next/src/server/dev/hot-reloader-rspack.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts)
- [packages/next/src/bundles/webpack/packages/lazy-compilation-web.js](https://github.com/blade47/next.js/blob/main/packages/next/src/bundles/webpack/packages/lazy-compilation-web.js)
- [packages/next/src/client/dev/noop-turbopack-hmr.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/noop-turbopack-hmr.ts)
- [packages/next/src/client/next-dev-turbopack.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/next-dev-turbopack.ts)
- [packages/next/src/server/lib/find-page-file.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/find-page-file.ts)
- [packages/next/src/shared/lib/utils.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/utils.ts)
</details>

## Overview

The Next.js development server and Hot Module Replacement (HMR) subsystem bridges local source files with browser-side execution, handling compilation orchestration, dynamic route resolution, and real-time code updates. When developers run `next dev`, the CLI spins up an isolated worker process via Node.js IPC (`child_process.fork`), initializing the core `DevServer` class and binding an underlying bundler service powered by either Webpack (`HotReloaderWebpack`), Rspack (`HotReloaderRspack`), or Turbopack (`createHotReloaderTurbopack`).
Sources: [packages/next/src/cli/next-dev.ts:394-427](https://github.com/blade47/next.js/blob/main/packages/next/src/cli/next-dev.ts#L394-L427)

Rather than building an entire project monolithically on startup, Next.js utilizes on-demand entry compilation and file system watching (`Watchpack`) to track entries incrementally. The subsystem coordinates multi-compiler pipelines (client, server, and edge server targets) while managing a WebSocket or EventSource communication loop. This ensures that compiler stats, syntax errors, module graph changes, and Fast Refresh payloads stream instantly to the browser runtime without requiring full page reloads.
Sources: [packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:234-276](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts#L234-L276)

```mermaid
flowchart TD
    CLI["next dev CLI (next-dev.ts)"] -->|forks child process| Server["DevServer / StartServer"]
    Server --> Setup["setupDevBundler()"]
    Setup -->|Choice: Turbo| Turbo["HotReloader (Turbopack)"]
    Setup -->|Choice: Webpack| Webpack["HotReloaderWebpack"]
    Setup -->|Choice: Rspack| Rspack["HotReloaderRspack"]
    Webpack --> OnDemand["onDemandEntryHandler()"]
    Rspack --> OnDemand
    OnDemand --> Watch["Watchpack File Watcher"]
    Webpack --> Middleware["WebpackHotMiddleware / WebSocket"]
    Middleware --> Browser["Browser HMR Runtime (React/Pages/App)"]
```
Sources: [packages/next/src/cli/next-dev.ts:394-427](https://github.com/blade47/next.js/blob/main/packages/next/src/cli/next-dev.ts#L394-L427), [packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:234-276](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts#L234-L276)

---

## Dev Server Initialization and CLI Orchestration

The development lifecycle begins in the Next.js CLI runner (`packages/next/src/cli/next-dev.ts`), which parses command flags—such as `--turbopack`, `--webpack`, `--port`, and `--inspect`—and prepares process environment variables before spawning the background worker server.
Sources: [packages/next/src/cli/next-dev.ts:45-63](https://github.com/blade47/next.js/blob/main/packages/next/src/cli/next-dev.ts#L45-L63)

When invoking `startServer`, the parent CLI forks a worker process with explicit Node.js options, custom memory allocations, and telemetry variables. The child communicates readiness through IPC messages (`nextWorkerReady`, `nextServerReady`), allowing the CLI manager to cleanly handle restarts (`RESTART_EXIT_CODE`), capture CPU profiles, and persist project metadata to `dev-state.json`.
Sources: [packages/next/src/cli/next-dev.ts:429-448](https://github.com/blade47/next.js/blob/main/packages/next/src/cli/next-dev.ts#L429-L448)

```typescript
child = fork(startServerPath, {
  stdio: 'inherit',
  execArgv,
  env: {
    ...defaultEnv,
    ...(isTurbopack ? { TURBOPACK: process.env.TURBOPACK } : undefined),
    __NEXT_DEV_SERVER: '1',
    NEXT_PRIVATE_WORKER: '1',
    NEXT_PRIVATE_TRACE_ID: traceId,
    NODE_OPTIONS: formattedNodeOptions,
  },
})
```
Sources: [packages/next/src/cli/next-dev.ts:394-427](https://github.com/blade47/next.js/blob/main/packages/next/src/cli/next-dev.ts#L394-L427)

---

## Bundler Service Abstraction and Router Integration

The `DevServer` class extends the production `Server` class, incorporating a `bundlerService` (`DevBundlerService`) interface that unifies operations between Webpack, Rspack, and Turbopack bundlers.
Sources: [packages/next/src/server/dev/next-dev-server.ts:123-143](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/next-dev-server.ts#L123-L143), [packages/next/src/server/lib/dev-bundler-service.ts:17-43](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/dev-bundler-service.ts#L17-L43)

`DevServer` overrides page component resolution methods (`findPageComponents`, `ensurePage`, `getCompilationError`) to delegate compilation requests directly to the active bundler service. If a page encounters compilation errors, `getCompilationError` inspects bundler diagnostics and wraps them in a `WrappedBuildError` to prevent duplicate console logging during request handling.
Sources: [packages/next/src/server/dev/next-dev-server.ts:976-1034](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/next-dev-server.ts#L976-L1034)

| Method / Property | Return Type | Purpose in DevServer |
| :--- | :--- | :--- |
| `ensurePage(opts)` | `Promise<void>` | Triggers compilation of a page entry on-demand if not already built. |
| `getCompilationError(page)` | `Promise<any>` | Retrieves compilation errors for a given page route from the bundler. |
| `appIsrManifest` | `Record<string, boolean>` | Exposes the Incremental Static Regeneration manifest state for cached routes. |
| `sendHmrMessage(msg)` | `void` | Broadcasts an HMR message to all active browser client sockets. |

Sources: [packages/next/src/server/dev/next-dev-server.ts:976-984](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/next-dev-server.ts#L976-L984), [packages/next/src/server/dev/next-dev-server.ts:1043-1045](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/next-dev-server.ts#L1043-L1045), [packages/next/src/server/lib/dev-bundler-service.ts:103-111](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/dev-bundler-service.ts#L103-L111), [packages/next/src/server/lib/dev-bundler-service.ts:132-135](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/dev-bundler-service.ts#L132-L135)

---

## On-Demand Entry Compilation

To optimize resource utilization, Next.js does not compile all pages upfront. Instead, the `onDemandEntryHandler` (`packages/next/src/server/dev/on-demand-entry-handler.ts`) tracks active route entries dynamically as requests arrive.
Sources: [packages/next/src/server/dev/on-demand-entry-handler.ts:541-570](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts#L541-L570)

Entries are identified in the multi-compiler graph via structured keys generated by `getEntryKey`:
`compilerType@pageBundleType@pageKey`
Sources: [packages/next/src/server/dev/on-demand-entry-handler.ts:116-125](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts#L116-L125)

For example, a client-side Pages router request for `/about` yields `client@pages@/about`, while an App router server file yields `server@app@app/page`.
Sources: [packages/next/src/server/dev/on-demand-entry-handler.ts:116-125](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts#L116-L125)

```mermaid
flowchart LR
    Request["Incoming Page Request"] --> Ensure["ensurePage()"]
    Ensure --> Check{"Entry exists & Built?"}
    Check -->|No| Add["Register Entry (ADDED)"]
    Add --> Compile["Compiler hooks.make (BUILDING)"]
    Compile --> Finish["Compiler hooks.done (BUILT)"]
    Check -->|Yes| Serve["Serve Compiled Output"]
```
Sources: [packages/next/src/server/dev/on-demand-entry-handler.ts:581-613](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts#L581-L613), [packages/next/src/server/dev/on-demand-entry-handler.ts:705-718](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts#L705-L718)

The on-demand handler manages entry lifecycles through three core states: `ADDED`, `BUILDING`, and `BUILT`. Inactive entries past `maxInactiveAge` are automatically disposed of to free memory.
Sources: [packages/next/src/server/dev/on-demand-entry-handler.ts:171-199](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts#L171-L199)

```typescript
export const ADDED = Symbol('added')
export const BUILDING = Symbol('building')
export const BUILT = Symbol('built')
```
Sources: [packages/next/src/server/dev/on-demand-entry-handler.ts:171-174](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts#L171-L174)

---

## Hot Module Replacement (HMR) and Webpack Hot Middleware

The Webpack HMR implementation (`WebpackHotMiddleware`) hooks into multi-compiler compilation events (`invalid`, `done`) across client, server, and edge-server compilers to broadcast build states to connected browser clients over WebSockets.
Sources: [packages/next/src/server/dev/hot-middleware.ts:73-104](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-middleware.ts#L73-L104)

```mermaid
sequenceDiagram
    participant Browser as Browser Client
    participant MW as WebpackHotMiddleware
    participant Compilers as Webpack MultiCompiler
    Compilers->>MW: compiler.hooks.done (Stats)
    MW->>MW: statsToJson() & getStatsForSyncEvent()
    MW-->>Browser: publish({ type: BUILT, hash, errors, warnings })
    Browser->>Browser: tryApplyUpdatesWebpack() or Fast Refresh
```
Sources: [packages/next/src/server/dev/hot-middleware.ts:113-117](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-middleware.ts#L113-L117), [packages/next/src/server/dev/hot-middleware.ts:214-229](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-middleware.ts#L214-L229)

When a compilation finishes, `WebpackHotMiddleware` computes whether server or client stats take precedence. If server compilation errors occur, server stats override client stats to ensure the error overlay displays backend/middleware compilation issues immediately.
Sources: [packages/next/src/server/dev/hot-middleware.ts:55-71](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-middleware.ts#L55-L71)

> [!NOTE]
> `WebpackHotMiddleware` prioritizes server compiler stats when `serverStats.stats.hasErrors()` is true, preventing situations where the client compilation succeeds independently while a server-side route or middleware fails silently.
Sources: [packages/next/src/server/dev/hot-middleware.ts:62-67](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-middleware.ts#L62-L67)

---

## Turbopack HMR Integration

When Turbopack is enabled (`--turbopack`), Next.js replaces Webpack-specific hot reloading with Turbopack's native HMR client and server coordination (`packages/next/src/server/dev/hot-reloader-turbopack.ts`).
Sources: [packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:234-247](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts#L234-L247)

The turbopack hot reloader maintains client subscription maps (`clientsWithoutHtmlRequestId`, `clientsByHtmlRequestId`) and enqueues compilation updates via `sendEnqueuedMessages`. If any active entry issue map contains non-warning errors, HMR event dispatches are delayed until compilation errors are fully resolved.
Sources: [packages/next/src/server/dev/hot-reloader-turbopack.ts:697-700](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-turbopack.ts#L697-L700), [packages/next/src/server/dev/hot-reloader-turbopack.ts:711-720](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-turbopack.ts#L711-L720)

```typescript
function sendEnqueuedMessages() {
  for (const [, issueMap] of currentEntryIssues) {
    if (
      [...issueMap.values()].filter((i) => i.severity !== 'warning').length >
      0
    ) {
      // During compilation errors we want to delay the HMR events until errors are fixed
      return
    }
  }
  // Broadcast enqueued messages to connected clients...
}
```
Sources: [packages/next/src/server/dev/hot-reloader-turbopack.ts:711-721](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-turbopack.ts#L711-L721)

---

## Rspack Persistent Cache Management

Rspack builds introduce persistent module graph caching that differs from Webpack's incremental design. Because the dev server starts with zero initial page entries, restoring from Rspack's persistent cache can purge module graphs if not managed.
Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:11-28](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L11-L28)

`HotReloaderRspack` (`packages/next/src/server/dev/hot-reloader-rspack.ts`) solves this by tracking successfully built page entries in `built-entries.json` under `.next/cache/rspack/`. After compilation completes, `afterCompile` verifies whether page files and entry paths still exist and validates their content hashes using SHA-256 before restoring them into the compiler state.
Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:29-77](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L29-L77)

```typescript
async function calculateFileHash(
  filePath: string,
  algorithm: string = 'sha256'
): Promise<string | undefined> {
  if (
    !(await fs.access(filePath).then(
      () => true,
      () => false
    ))
  ) {
    return
  }
  const fileBuffer = await fs.readFile(filePath)
  const hash = createHash(algorithm)
  hash.update(fileBuffer)
  return hash.digest('hex')
}
```
Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:227-243](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L227-L243)

---

## Client-Side HMR Runtimes (Pages and App Router)

Browser-side HMR behavior is split between Pages Router (`packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts`) and App Router (`packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx`).
Sources: [packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts:95-125](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts#L95-L125), [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:123-145](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L123-L145)

Both clients register message listeners via WebSocket connections to handle incoming synchronization payloads (`HMR_MESSAGE_SENT_TO_BROWSER.SYNC`), build successes (`handleSuccess`), and compilation errors (`handleErrors`).
Sources: [packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts:98-104](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts#L98-L104), [packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts:142-147](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts#L142-L147), [packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts:210-215](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts#L210-L215)

```typescript
// Attempt to update code on the fly, fall back to a hard reload.
function tryApplyUpdatesWebpack(sendMessage: (message: string) => void) {
  if (!isUpdateAvailable() || !canApplyUpdates()) {
    resolvePendingHotUpdateWebpack()
    dispatcher.onBuildOk()
    reportHmrLatency(sendMessage, [], webpackStartMsSinceEpoch!, Date.now())
    return
  }
  // Applies module hot updates via module.hot.check()
}
```
Sources: [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:148-154](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L148-L154)

If webpack hot module replacement fails or runtime errors are present (`RuntimeErrorHandler.hadRuntimeError`), the client triggers `performFullReload`, passing stack trace details and dependency chains to prevent inconsistent application state.
Sources: [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:123-145](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L123-L145), [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:160-167](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L160-L167)

## Related

- [Dev Error Overlay](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development-and-diagnostics/dev-error-overlay)
- [Bundler Integration](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development-and-diagnostics/bundler-integration)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
