Skip to content

Instantly share code, notes, and snippets.

@ScriptedAlchemy
Last active June 25, 2025 06:45
Show Gist options
  • Save ScriptedAlchemy/cf67a9e51f155dc3ad57d44d28128c94 to your computer and use it in GitHub Desktop.
Save ScriptedAlchemy/cf67a9e51f155dc3ad57d44d28128c94 to your computer and use it in GitHub Desktop.
next hmr flow

Next.js Server-Side Hot Module Replacement (HMR) Architecture

Overview

Next.js implements a sophisticated server-side HMR system that coordinates multiple webpack compilers, handles server component updates, and manages complex caching mechanisms. This document provides a comprehensive analysis of the server-side HMR architecture.

High-Level Server Architecture

graph TB
    subgraph "Development Server"
        DevServer[Next.js Dev Server]
        HotReloader[Hot Reloader Webpack/Turbopack]
        OnDemandHandler[On-Demand Entry Handler]
        HotMiddleware[WebSocket Hot Middleware]
    end
    
    subgraph "Multi-Compiler System"
        ClientCompiler[Client Compiler]
        ServerCompiler[Server Compiler] 
        EdgeCompiler[Edge-Server Compiler]
    end
    
    subgraph "Cache Management"
        RequireCache[Node.js Require Cache]
        ModuleContext[Edge Module Context]
        ManifestCache[Build Manifest Cache]
    end
    
    subgraph "File System"
        SourceFiles[Source Files]
        PageFiles[Page Components]
        ServerComponents[Server Components]
        MiddlewareFiles[Middleware/Edge Files]
    end
    
    DevServer --> HotReloader
    HotReloader --> ClientCompiler
    HotReloader --> ServerCompiler
    HotReloader --> EdgeCompiler
    HotReloader --> OnDemandHandler
    HotReloader --> HotMiddleware
    
    ClientCompiler --> RequireCache
    ServerCompiler --> RequireCache
    EdgeCompiler --> ModuleContext
    
    SourceFiles --> ClientCompiler
    PageFiles --> ServerCompiler
    ServerComponents --> ServerCompiler
    MiddlewareFiles --> EdgeCompiler
    
    RequireCache --> ManifestCache
    ModuleContext --> ManifestCache
Loading

Core Server Components

1. Hot Reloader Classes

classDiagram
    class HotReloaderWebpack {
        <<packages/next/src/server/dev/hot-reloader-webpack.ts>>
        -multiCompiler: webpack.MultiCompiler
        -clientStats: webpack.Stats
        -serverStats: webpack.Stats
        -edgeServerStats: webpack.Stats
        -hasServerError: boolean
        -onDemandEntries: OnDemandEntryHandler
        -hotMiddleware: WebpackHotMiddleware
        +constructor()
        +start()
        +run(req, res, parsedUrl)
        +onHMR(client)
        +send(action)
        +ensurePage(options)
        +getCompilationErrors()
        +invalidate()
        +buildFallbackError()
        +setHmrServerError()
        +clearHmrServerError()
        +refreshServerComponents(hash)
        +close()
        -trackPageChanges()
    }
    
    class HotReloaderTurbopack {
        <<packages/next/src/server/dev/hot-reloader-turbopack.ts>>
        -project: Project
        -buildManifests: Map
        -subscriptions: Set
        -changeSubscriptions: Map
        +run(req, res, parsedUrl)
        +onHMR(client)
        +send(action)
        +ensurePage(options)
        +getCompilationErrors()
        +invalidate()
        +start()
        +close()
        +getVersionInfo()
        -handleEntrypointsSubscription()
        -subscribeToChanges()
        -clearModuleContext(target)
    }
    
    class OnDemandEntryHandler {
        <<packages/next/src/server/dev/on-demand-entry-handler.ts>>
        -entries: Map~string, EntryData~
        -lastAccessPages: Set~string~
        -doneCallbacks: Map
        -pagesDir: string
        -appDir: string
        +ensurePage(options)
        +onHMR(client)
        -findPagePathData()
        -getEntryKey()
        -getEntries()
        -getInvalidator()
        -disposeInactiveEntries()
        -handlePing(page)
    }
    
    class WebpackHotMiddleware {
        <<packages/next/src/server/dev/hot-middleware.ts>>
        -eventStream: EventStream
        -clients: Set~WebSocket~
        -clientLatestStats: webpack.Stats
        -serverLatestStats: webpack.Stats
        -edgeServerLatestStats: webpack.Stats
        +constructor()
        +onClientInvalid()
        +onClientDone(stats)
        +onServerInvalid()
        +onServerDone(stats)
        +onEdgeServerInvalid()
        +onEdgeServerDone(stats)
        +onHMR(client)
        +publishStats(action, stats)
        +publish(payload)
        +close()
    }
    
    HotReloaderWebpack --> OnDemandEntryHandler
    HotReloaderWebpack --> WebpackHotMiddleware
    HotReloaderTurbopack --> WebpackHotMiddleware
Loading

2. Multi-Compiler Coordination

sequenceDiagram
    participant FS as File System
    participant Entry as On-Demand Handler
    participant Client as Client Compiler
    participant Server as Server Compiler
    participant Edge as Edge Compiler
    participant HotReloader as Hot Reloader
    participant WS as WebSocket Middleware
    
    FS->>Entry: File change detected
    Entry->>Entry: Determine affected compilers
    
    alt Client-side change
        Entry->>Client: Invalidate client entries
        Client->>Client: Compile client bundle
        Client->>HotReloader: Client compilation done
    end
    
    alt Server component change
        Entry->>Server: Invalidate server entries
        Server->>Server: Compile server bundle
        Server->>HotReloader: Server compilation done
        HotReloader->>HotReloader: Clear require cache
        HotReloader->>HotReloader: Reset fetch cache
    end
    
    alt Edge runtime change
        Entry->>Edge: Invalidate edge entries
        Edge->>Edge: Compile edge bundle
        Edge->>HotReloader: Edge compilation done
        HotReloader->>HotReloader: Clear module context
    end
    
    HotReloader->>HotReloader: Coordinate compilation results
    HotReloader->>WS: Send HMR action
    WS->>WS: Broadcast to clients
Loading

Server-Side Webpack Integration

1. Custom HMR Plugin Architecture

graph TB
    subgraph "Webpack Configuration"
        WebpackConfig["webpack-config.ts<br/>packages/next/src/build/<br/>webpack-config.ts"]
        DevPlugins["Development Plugins<br/>(conditionally added in dev mode)"]
    end
    
    subgraph "Custom HMR Plugins"
        RequireCacheHotReloader["NextJsRequireCacheHotReloader<br/>packages/next/src/build/webpack/plugins/<br/>nextjs-require-cache-hot-reloader.ts"]
        HMRPlugin["HotModuleReplacementPlugin<br/>(webpack built-in)"]
        ReactRefreshPlugin["ReactRefreshWebpackPlugin<br/>packages/react-refresh-utils/<br/>ReactRefreshWebpackPlugin.ts"]
    end
    
    subgraph "Cache Management"
        RequireHooks["require-hook.ts<br/>packages/next/src/server/<br/>require-hook.ts"]
        RequireCache["require-cache.ts<br/>packages/next/src/server/dev/<br/>require-cache.ts"]
        ModuleContext["Module Context Manager<br/>packages/next/src/server/web/<br/>sandbox/context.ts"]
    end
    
    WebpackConfig --> DevPlugins
    DevPlugins --> RequireCacheHotReloader
    DevPlugins --> HMRPlugin
    DevPlugins --> ReactRefreshPlugin
    
    RequireCacheHotReloader --> RequireCache
    RequireCacheHotReloader --> ModuleContext
    RequireCache --> RequireHooks
Loading

2. Server Cache Invalidation Flow

flowchart TD
    A[File Change Detected] --> B{Determine Compilation Target}
    
    B -->|Client Only| C[Client Compiler]
    B -->|Server Component| D[Server Compiler]
    B -->|Edge Runtime| E[Edge Compiler]
    B -->|Multi-target| F[Multiple Compilers]
    
    C --> G[Standard Webpack HMR]
    D --> H[Server Cache Invalidation]
    E --> I[Edge Context Clearing]
    F --> J[Coordinated Compilation]
    
    H --> K[Clear Require Cache]
    H --> L[Delete Module References]
    H --> M[Reset Fetch Cache]
    
    I --> N[Clear Module Context]
    I --> O[Reset Timers/Intervals]
    I --> P[Remove Context Paths]
    
    K --> Q[NextJsRequireCacheHotReloader]
    L --> Q
    M --> Q
    N --> R[Edge Runtime Sandbox]
    O --> R
    P --> R
    
    Q --> S[Emit assetEmitted Hook]
    Q --> T[Emit afterEmit Hook]
    R --> U[Clear Module Contexts]
    
    G --> V[HMR Message Generation]
    S --> V
    T --> V
    U --> V
    
    V --> W[WebSocket Broadcast]
Loading

Server Compilation Pipeline

1. Entry Point Management

stateDiagram-v2
    [*] --> Idle
    
    Idle --> PageRequest : HTTP Request
    PageRequest --> CheckEntry : Check if entry exists
    
    CheckEntry --> EntryExists : Entry found
    CheckEntry --> CreateEntry : Entry missing
    
    CreateEntry --> AddToWebpack : Add to webpack entries
    AddToWebpack --> Compiling : Start compilation
    
    EntryExists --> CheckUpToDate : Verify compilation status
    CheckUpToDate --> Serving : Up to date
    CheckUpToDate --> Compiling : Needs recompilation
    
    Compiling --> CompilationDone : Compilation finished
    CompilationDone --> HasErrors : Check for errors
    
    HasErrors --> ErrorHandling : Errors found
    HasErrors --> Serving : No errors
    
    ErrorHandling --> Serving : Fallback to error page
    Serving --> Idle : Response sent
    
    Idle --> FileChange : File system change
    FileChange --> InvalidateEntry : Mark entries dirty
    InvalidateEntry --> Idle : Ready for recompilation
Loading

2. Multi-Compiler Synchronization

graph TB
    subgraph "Compilation Coordination"
        A[File Change Detected]
        B{Determine Affected Compilers}
        C[Client Compilation]
        D[Server Compilation]
        E[Edge Compilation]
        F[Compilation Results]
        G{All Compilers Done?}
        H[Aggregate Results]
        I[Send HMR Message]
    end
    
    subgraph "Error Prioritization"
        J{Has Server Errors?}
        K[Show Server Errors]
        L{Has Client Errors?}
        M[Show Client Errors]
        N{Has Edge Errors?}
        O[Show Edge Errors]
        P[Success State]
    end
    
    A --> B
    B --> C
    B --> D
    B --> E
    
    C --> F
    D --> F
    E --> F
    
    F --> G
    G -->|No| G
    G -->|Yes| H
    
    H --> J
    J -->|Yes| K
    J -->|No| L
    L -->|Yes| M
    L -->|No| N
    N -->|Yes| O
    N -->|No| P
    
    K --> I
    M --> I
    O --> I
    P --> I
Loading

Server Component HMR Handling

1. Server Component Change Detection

sequenceDiagram
    participant FS as File System Watcher
    participant Webpack as Webpack Compiler
    participant Tracker as Page Change Tracker
    participant Cache as Server Cache
    participant HMR as HMR System
    participant Client as Browser Client
    
    FS->>Webpack: Server component file changed
    Webpack->>Webpack: Compile with RSC layer tracking
    Webpack->>Tracker: Emit compilation stats
    
    Tracker->>Tracker: Calculate module hashes
    Note over Tracker: Separate client & server layer hashes
    
    Tracker->>Tracker: Compare with previous hashes
    alt Server component changed
        Tracker->>Cache: Clear server module cache
        Cache->>Cache: Delete require cache entries
        Cache->>Cache: Reset fetch cache
        Tracker->>HMR: Mark as server component change
        HMR->>Client: Send SERVER_COMPONENT_CHANGES
        Client->>Client: router.hmrRefresh()
    else Client component changed
        Tracker->>HMR: Mark as client change
        HMR->>Client: Send BUILT message
        Client->>Client: Apply React Fast Refresh
    end
Loading

2. Server Component Layer Architecture

graph TB
    subgraph "Webpack Layers"
        RSCLayer[reactServerComponents]
        SSRLayer[serverSideRendering]
        ClientLayer[appPagesBrowser/pagesDirBrowser]
        EdgeLayer[pagesDirEdge/apiEdge/middleware]
    end
    
    subgraph "Module Resolution"
        ServerComponents[Server Components]
        ClientComponents[Client Components]
        SharedComponents[Shared Components]
        EdgeFunctions[Edge Functions]
    end
    
    subgraph "Compilation Targets"
        ServerBundle[Server Bundle]
        ClientBundle[Client Bundle]
        EdgeBundle[Edge Bundle]
    end
    
    ServerComponents --> RSCLayer
    ClientComponents --> ClientLayer
    SharedComponents --> SSRLayer
    EdgeFunctions --> EdgeLayer
    
    RSCLayer --> ServerBundle
    SSRLayer --> ServerBundle
    ClientLayer --> ClientBundle
    EdgeLayer --> EdgeBundle
Loading

Cache Management System

1. Multi-Level Cache Architecture

graph TB
    subgraph "Node.js Runtime"
        RequireCache[require.cache]
        ModuleChildren[Module.children]
        ModuleParent[Module.parent]
    end
    
    subgraph "Edge Runtime"
        EdgeModuleContext[Module Context Map]
        EdgeSandbox[Sandbox Context]
        TimersManager[Timers/Intervals Manager]
    end
    
    subgraph "Next.js Caches"
        ManifestCache[Build Manifest Cache]
        FetchCache[Server Fetch Cache]
        IncrementalCache[Incremental Cache]
    end
    
    subgraph "Cache Invalidation"
        DeleteCache[deleteCache]
        ClearModuleContext[clearModuleContext]
        ResetFetch[resetFetch]
    end
    
    RequireCache --> DeleteCache
    ModuleChildren --> DeleteCache
    ModuleParent --> DeleteCache
    
    EdgeModuleContext --> ClearModuleContext
    EdgeSandbox --> ClearModuleContext
    TimersManager --> ClearModuleContext
    
    ManifestCache --> DeleteCache
    FetchCache --> ResetFetch
    IncrementalCache --> DeleteCache
Loading

2. Cache Invalidation Strategy

flowchart TD
    A[Module Change Detected] --> B{Module Type?}
    
    B -->|Server Component| C[Clear Server Caches]
    B -->|Client Component| D[Standard HMR]
    B -->|Edge Function| E[Clear Edge Context]
    B -->|Shared Module| F[Clear All Caches]
    
    C --> G[Delete from require.cache]
    C --> H[Clear manifest cache]
    C --> I[Reset server fetch cache]
    
    E --> J[Clear module contexts]
    E --> K[Reset timers/intervals]
    E --> L[Remove context paths]
    
    F --> G
    F --> H
    F --> I
    F --> J
    F --> K
    F --> L
    
    G --> M[Update Module References]
    H --> N[Rebuild Manifest]
    I --> O[Reset Request Cache]
    J --> P[Recreate Edge Context]
    K --> P
    L --> P
    
    M --> Q[Send HMR Update]
    N --> Q
    O --> Q
    P --> Q
Loading

HMR Message Protocol

1. Server-Side Message Generation

graph TB
    subgraph "Compilation Events"
        A[Client Compilation Done]
        B[Server Compilation Done]
        C[Edge Compilation Done]
    end
    
    subgraph "Change Analysis"
        D[Calculate Page Changes]
        E[Determine Change Type]
        F[Check for Errors]
    end
    
    subgraph "Message Types"
        G[BUILDING]
        H[BUILT]
        I[SERVER_COMPONENT_CHANGES]
        J[MIDDLEWARE_CHANGES]
        K[RELOAD_PAGE]
        L[SYNC]
    end
    
    subgraph "WebSocket Broadcast"
        M[Format Message Payload]
        N[Send to All Clients]
        O[Handle Client Acknowledgments]
    end
    
    A --> D
    B --> D
    C --> D
    
    D --> E
    E --> F
    
    F --> G
    F --> H
    F --> I
    F --> J
    F --> K
    F --> L
    
    G --> M
    H --> M
    I --> M
    J --> M
    K --> M
    L --> M
    
    M --> N
    N --> O
Loading

2. Error Prioritization Flow

flowchart TD
    A[Compilation Results Available] --> B{Server Compilation Errors?}
    
    B -->|Yes| C[Collect Server Errors]
    B -->|No| D{Client Compilation Errors?}
    
    C --> E[Format Server Error Messages]
    E --> F[Send RELOAD_PAGE with Server Errors]
    
    D -->|Yes| G[Collect Client Errors]
    D -->|No| H{Edge Compilation Errors?}
    
    G --> I[Format Client Error Messages]
    I --> J[Send RELOAD_PAGE with Client Errors]
    
    H -->|Yes| K[Collect Edge Errors]
    H -->|No| L[No Compilation Errors]
    
    K --> M[Format Edge Error Messages]
    M --> N[Send RELOAD_PAGE with Edge Errors]
    
    L --> O{Changes Detected?}
    O -->|Server Components| P[Send SERVER_COMPONENT_CHANGES]
    O -->|Client Changes| Q[Send BUILT]
    O -->|Middleware Changes| R[Send MIDDLEWARE_CHANGES]
    O -->|No Changes| S[Send SYNC]
    
    F --> T[Client Handles Error Display]
    J --> T
    N --> T
    P --> U[Client Handles Server Refresh]
    Q --> V[Client Handles HMR Update]
    R --> W[Client Handles Middleware Restart]
    S --> X[Client Syncs State]
Loading

Key File Responsibilities

Core Server HMR Files

File Path Class/Function Primary Responsibility
/packages/next/src/server/dev/hot-reloader-webpack.ts HotReloaderWebpack Main HMR orchestrator for webpack builds
/packages/next/src/server/dev/hot-reloader-turbopack.ts createHotReloaderTurbopack() Turbopack-specific HMR implementation
/packages/next/src/server/dev/hot-middleware.ts WebpackHotMiddleware WebSocket communication layer
/packages/next/src/server/dev/on-demand-entry-handler.ts onDemandEntryHandler() Dynamic entry point management
/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts NextJsRequireCacheHotReloader Server-side cache invalidation plugin
/packages/next/src/server/dev/require-cache.ts deleteCache(), deleteFromRequireCache() Node.js require cache management
/packages/next/src/server/require-hook.ts Module resolution hooks Custom require/resolve implementation
/packages/next/src/server/web/sandbox/context.ts clearModuleContext() Edge runtime context management
/packages/next/src/server/dev/hot-reloader-types.ts HMR_ACTIONS_SENT_TO_BROWSER HMR type definitions and interfaces

Integration and Configuration Files

File Path Class/Function Primary Responsibility
/packages/next/src/build/webpack-config.ts webpack configuration Webpack configuration with HMR plugins
/packages/next/src/server/lib/dev-bundler-service.ts DevBundlerService Unified bundler interface
/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts setupDevBundler() Development bundler initialization
/packages/next/src/server/dev/next-dev-server.ts NextDevServer Development server with HMR integration
/packages/next/src/shared/lib/constants.ts COMPILER_NAMES Compiler type definitions
/packages/next/src/server/dev/middleware-webpack.ts getOverlayMiddleware() Development middleware functions
/packages/next/src/server/dev/turbopack-utils.ts Turbopack utilities Route handling and issue processing

React Fast Refresh Integration Files

File Path Class/Function Primary Responsibility
/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts ReactRefreshWebpackPlugin Webpack plugin for React Fast Refresh
/packages/react-refresh-utils/loader.ts ReactRefreshLoader Webpack loader for React components
/packages/react-refresh-utils/runtime.ts React Refresh globals Global React Refresh runtime setup
/packages/react-refresh-utils/internal/helpers.ts RefreshHelpers React Refresh utility functions
/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts Module runtime Module-level React Refresh integration

Client-Side HMR Files

File Path Class/Function Primary Responsibility
/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx HotReload component App Router HMR client
/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts connect() function Pages Router HMR client
/packages/next/src/client/dev/hot-reloader/app/use-websocket.ts useWebsocket() hook WebSocket management for App Router
/packages/next/src/client/dev/hot-reloader/pages/websocket.ts connectHMR() WebSocket client for Pages Router
/packages/next/src/client/dev/hot-middleware-client.ts HMR middleware client Pages Router HMR event handler
/packages/next/src/client/dev/on-demand-entries-client.ts On-demand entries ping Page activity tracking client
/packages/next/src/client/dev/hot-reloader/turbopack-hot-reloader-common.ts TurbopackHmr class Turbopack HMR client integration
/packages/next/src/client/dev/report-hmr-latency.ts reportHmrLatency() HMR performance metrics reporting

Build System Integration Files

File Path Class/Function Primary Responsibility
/packages/next/src/build/webpack/loaders/next-style-loader/index.ts CSS HMR loader CSS hot reloading with module support
/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts BuildManifestPlugin Asset manifest with React Refresh entries
/packages/next/src/build/entries.ts Entry configuration React Refresh runtime entry points
/packages/next/src/build/swc/index.ts Turbopack integration HMR events and identifier subscriptions

Performance Optimizations

1. Selective Compilation Strategy

  • On-demand compilation: Only compile pages when requested
  • Incremental builds: Leverage webpack's persistent cache
  • Multi-compiler coordination: Optimize compilation order (client before server)
  • Entry lifecycle management: Dispose inactive entries to prevent memory leaks

2. Cache Optimization

  • Granular invalidation: Only clear caches for changed modules
  • Context preservation: Maintain edge runtime contexts when possible
  • Fetch cache management: Reset server-side fetch cache strategically
  • Module reference cleanup: Properly clean parent/child module relationships

3. Error Recovery

  • Compilation error prioritization: Show most critical errors first
  • Graceful degradation: Continue serving when possible during errors
  • Error boundary integration: Provide detailed error information with source maps
  • Automatic retry mechanisms: Retry compilation on transient failures

Complete File Dependencies and Relationships

Server-Side HMR File Dependency Graph

graph TB
    subgraph "Entry Points"
        DevServer["next dev server<br/>packages/next/src/server/dev/<br/>next-dev-server.ts"]
        SetupBundler["setupDevBundler()<br/>packages/next/src/server/lib/<br/>router-utils/setup-dev-bundler.ts"]
    end
    
    subgraph "HMR Orchestration Layer"
        HotReloaderWebpack["HotReloaderWebpack<br/>packages/next/src/server/dev/<br/>hot-reloader-webpack.ts"]
        HotReloaderTurbopack["createHotReloaderTurbopack()<br/>packages/next/src/server/dev/<br/>hot-reloader-turbopack.ts"]
        HotMiddleware["WebpackHotMiddleware<br/>packages/next/src/server/dev/<br/>hot-middleware.ts"]
        HMRTypes["HMR Types & Actions<br/>packages/next/src/server/dev/<br/>hot-reloader-types.ts"]
    end
    
    subgraph "Build Pipeline Integration"
        WebpackConfig["webpack-config.ts<br/>packages/next/src/build/<br/>webpack-config.ts"]
        RequireCachePlugin["NextJsRequireCacheHotReloader<br/>packages/next/src/build/webpack/plugins/<br/>nextjs-require-cache-hot-reloader.ts"]
        ReactRefreshPlugin["ReactRefreshWebpackPlugin<br/>packages/react-refresh-utils/<br/>ReactRefreshWebpackPlugin.ts"]
        BuildManifestPlugin["BuildManifestPlugin<br/>packages/next/src/build/webpack/plugins/<br/>build-manifest-plugin.ts"]
    end
    
    subgraph "Entry Management"
        OnDemandHandler["onDemandEntryHandler()<br/>packages/next/src/server/dev/<br/>on-demand-entry-handler.ts"]
        DevBundlerService["DevBundlerService<br/>packages/next/src/server/lib/<br/>dev-bundler-service.ts"]
        Entries["Entry Configuration<br/>packages/next/src/build/<br/>entries.ts"]
    end
    
    subgraph "Cache Management Layer"
        RequireCache["deleteCache() & deleteFromRequireCache()<br/>packages/next/src/server/dev/<br/>require-cache.ts"]
        RequireHook["Module Resolution Hooks<br/>packages/next/src/server/<br/>require-hook.ts"]
        ModuleContext["clearModuleContext()<br/>packages/next/src/server/web/<br/>sandbox/context.ts"]
        SandboxIndex["Sandbox Operations<br/>packages/next/src/server/web/<br/>sandbox/index.ts"]
    end
    
    subgraph "Client Communication"
        AppHMR["HotReload Component<br/>packages/next/src/client/dev/<br/>hot-reloader/app/hot-reloader-app.tsx"]
        PagesHMR["connect() Function<br/>packages/next/src/client/dev/<br/>hot-reloader/pages/hot-reloader-pages.ts"]
        UseWebSocket["useWebsocket() Hook<br/>packages/next/src/client/dev/<br/>hot-reloader/app/use-websocket.ts"]
        WebSocketClient["connectHMR()<br/>packages/next/src/client/dev/<br/>hot-reloader/pages/websocket.ts"]
        HMRMiddlewareClient["HMR Middleware Client<br/>packages/next/src/client/dev/<br/>hot-middleware-client.ts"]
    end
    
    subgraph "React Integration"
        ReactRefreshLoader["ReactRefreshLoader<br/>packages/react-refresh-utils/<br/>loader.ts"]
        ReactRefreshRuntime["React Refresh Runtime<br/>packages/react-refresh-utils/<br/>runtime.ts"]
        ReactRefreshHelpers["RefreshHelpers<br/>packages/react-refresh-utils/<br/>internal/helpers.ts"]
        ReactModuleRuntime["Module Runtime<br/>packages/react-refresh-utils/<br/>internal/ReactRefreshModule.runtime.ts"]
    end
    
    subgraph "Development Utilities"
        TurbopackUtils["Turbopack Utilities<br/>packages/next/src/server/dev/<br/>turbopack-utils.ts"]
        MiddlewareWebpack["getOverlayMiddleware()<br/>packages/next/src/server/dev/<br/>middleware-webpack.ts"]
        OnDemandClient["On-Demand Entries Client<br/>packages/next/src/client/dev/<br/>on-demand-entries-client.ts"]
        HMRLatency["reportHmrLatency()<br/>packages/next/src/client/dev/<br/>report-hmr-latency.ts"]
    end
    
    %% Entry Point Dependencies
    DevServer --> SetupBundler
    SetupBundler --> HotReloaderWebpack
    SetupBundler --> HotReloaderTurbopack
    
    %% HMR Orchestration Dependencies
    HotReloaderWebpack --> HotMiddleware
    HotReloaderWebpack --> OnDemandHandler
    HotReloaderWebpack --> HMRTypes
    HotReloaderTurbopack --> HotMiddleware
    HotReloaderTurbopack --> HMRTypes
    HotReloaderTurbopack --> TurbopackUtils
    
    %% Build Pipeline Dependencies
    HotReloaderWebpack --> WebpackConfig
    WebpackConfig --> RequireCachePlugin
    WebpackConfig --> ReactRefreshPlugin
    WebpackConfig --> BuildManifestPlugin
    WebpackConfig --> Entries
    
    %% Entry Management Dependencies
    HotReloaderWebpack --> OnDemandHandler
    HotReloaderTurbopack --> DevBundlerService
    OnDemandHandler --> DevBundlerService
    
    %% Cache Management Dependencies
    RequireCachePlugin --> RequireCache
    RequireCachePlugin --> ModuleContext
    HotReloaderWebpack --> RequireCache
    HotReloaderTurbopack --> ModuleContext
    RequireCache --> RequireHook
    ModuleContext --> SandboxIndex
    
    %% Client Communication Dependencies
    HotMiddleware -.WebSocket.-> AppHMR
    HotMiddleware -.WebSocket.-> PagesHMR
    HotMiddleware -.WebSocket.-> HMRMiddlewareClient
    AppHMR --> UseWebSocket
    PagesHMR --> WebSocketClient
    AppHMR --> HMRLatency
    PagesHMR --> HMRLatency
    OnDemandHandler -.-> OnDemandClient
    
    %% React Integration Dependencies
    ReactRefreshPlugin --> ReactRefreshLoader
    ReactRefreshPlugin --> ReactRefreshRuntime
    ReactRefreshLoader --> ReactRefreshHelpers
    ReactRefreshLoader --> ReactModuleRuntime
    ReactRefreshRuntime --> ReactRefreshHelpers
    
    %% Development Utilities Dependencies
    DevServer --> MiddlewareWebpack
    HotReloaderWebpack --> MiddlewareWebpack
Loading

Component Integration Matrix

Component Webpack Turbopack Client Server Edge Cache
HotReloaderWebpack ✅ Primary ✅ WebSocket ✅ Primary ✅ Compiler ✅ Require
HotReloaderTurbopack ✅ Primary ✅ WebSocket ✅ Primary ✅ Compiler ✅ Context
WebpackHotMiddleware ✅ Events ✅ Events ✅ WebSocket ✅ Stats ✅ Stats
OnDemandEntryHandler ✅ Entries ✅ Ping ✅ Pages ✅ Routes
NextJsRequireCacheHotReloader ✅ Plugin ✅ Cache ✅ Context ✅ Primary
ReactRefreshWebpackPlugin ✅ Plugin ✅ Runtime
DevBundlerService ✅ Interface ✅ Interface ✅ Pages ✅ Routes

App Router vs Pages Router HMR Flow Differences

Next.js implements different HMR strategies for App Router and Pages Router, reflecting their architectural differences:

App Router HMR Flow

sequenceDiagram
    participant Client as App Router Client
    participant WS as WebSocket (/_next/webpack-hmr)
    participant HMRClient as App HMR Component
    participant Router as App Router
    participant RSC as Server Components
    
    Client->>WS: Connect via useWebsocket hook
    WS->>HMRClient: serverComponentChanges + hash
    HMRClient->>HMRClient: Set cookie with hash
    HMRClient->>Router: router.hmrRefresh()
    Router->>Router: startTransition for smooth update
    Router->>RSC: Fetch with isHmrRefresh: true
    RSC->>Router: Updated server component tree
    Router->>Client: Selective cache invalidation
    Note over Client: State preserved, no full reload
Loading

Key Characteristics:

  • React Hook-based: Uses useWebsocket() and useEffect() for WebSocket management
  • Server Component Support: Custom hmrRefresh() method for RSC updates
  • State Preservation: Selective cache invalidation preserves navigation state
  • Smooth Transitions: Uses React 18 startTransition() for better UX
  • Complex Routing: Handles parallel routes, nested layouts, and slots

Pages Router HMR Flow

sequenceDiagram
    participant Client as Pages Router Client
    participant WS as WebSocket (/_next/webpack-hmr)
    participant HMRClient as Pages HMR Handler
    participant Router as Pages Router
    participant Module as Module System
    
    Client->>WS: Connect via connectHMR()
    WS->>HMRClient: built + hash
    HMRClient->>HMRClient: Fetch HMR manifest
    HMRClient->>Module: Apply webpack HMR
    Module->>Router: Standard module replacement
    Router->>Client: Page-level updates
    Note over Client: Traditional HMR or full reload
Loading

Key Characteristics:

  • Imperative Management: Direct WebSocket connection without React hooks
  • Traditional HMR: Standard webpack module replacement flow
  • Full Reloads: Server component changes trigger window.location.reload()
  • Simpler Architecture: Page-based routing without complex nesting
  • Backward Compatibility: Maintains compatibility with older Next.js patterns

Integration Differences

Feature App Router Pages Router
WebSocket Management React hooks (useWebsocket) Direct connection (connectHMR)
Server Components router.hmrRefresh() with state preservation Full page reload
Routing Updates ACTION_HMR_REFRESH with selective invalidation Standard module replacement
Error Boundaries AppDevOverlayErrorBoundary Basic error overlay
State Management React context integration Global window object
Entry Points Complex with parallel routes/slots Simple page-to-file mapping

Summary

Next.js server-side HMR is a comprehensive system that:

  1. Coordinates multiple webpack compilers (client, server, edge) with intelligent synchronization
  2. Provides specialized server component handling with granular cache invalidation
  3. Implements sophisticated cache management across Node.js require cache and Edge runtime contexts
  4. Uses WebSocket communication for real-time development updates
  5. Optimizes for performance through on-demand compilation and selective invalidation
  6. Handles complex error scenarios with prioritization and graceful recovery

The system involves 50+ files across multiple packages, with precise integration points:

  • 9 core server HMR classes/functions handling orchestration and coordination
  • 5 webpack plugins providing cache invalidation and React Fast Refresh
  • 8 client-side components managing WebSocket communication and UI updates
  • 12 cache management utilities handling Node.js and Edge runtime contexts
  • 15+ build system integration points coordinating compilation and asset management

This architecture enables Next.js to provide seamless server-side development experience while maintaining consistency across multiple runtime environments and supporting advanced React features like Server Components.

Integrating a Custom HMR Client with Next.js

This document outlines a strategy for integrating a custom Hot Module Replacement (HMR) client with the Next.js development server. It is based on an analysis of the Next.js Server-Side HMR Architecture and a custom, webpack-based HMR client implementation.

1. Understanding the Next.js HMR Environment

Before integration, it's crucial to understand the key aspects of the Next.js HMR system.

graph LR
    subgraph "Next.js Dev Server"
        direction LR
        A[WebSocket Server]
        B[HMR Endpoints]
    end
    subgraph "Custom HMR Client"
        direction LR
        C[WebSocket Listener]
        D[Update Applier]
    end

    A -- HMR Events --> C
    C -- Notifies --> D
    D -- Fetches Update --> B
    B -- Serves Artifacts --> D
    D -- Applies Update --> D
Loading

Key Characteristics:

  • WebSocket Communication: The Next.js dev server communicates with clients via a WebSocket connection established at /_next/webpack-hmr.
  • Message Protocol: It uses a JSON-based message protocol with an action field to signify different events.
  • HMR Artifacts: When an update is ready, the server doesn't push the code directly. Instead, it sends a message containing a hash. The client is responsible for fetching the HMR manifest (.hot-update.json) and the updated chunks (.hot-update.js) from dedicated endpoints.
  • Specialized Updates: Next.js distinguishes between different types of updates. Changes in client-side components trigger a standard HMR flow. However, changes in server-side code (like Server Components or middleware) trigger messages that instruct the client to perform a full page reload or a special router refresh, as these cannot be hot-swapped in the browser.

HMR Message Protocol

Your client must handle the following actions sent by the Next.js WebSocket server:

Action Payload Client Responsibility
building {} (Optional) Show a "compiling" indicator.
built, sync { hash: string, ... } Fetch HMR artifacts and apply the update.
serverComponentChanges { hash: string } Trigger a "soft" navigation or a full page reload. Do not attempt HMR.
middlewareChanges {} Trigger a full page reload. Uses event field instead of action.
reloadPage {} Trigger a full page reload (window.location.reload()).
serverError { errors: string[] } Display compilation errors.

2. Adapting Your HMR Client

Your current HMRClient is designed around a poll-based UpdateProvider. To integrate with Next.js's push-based WebSocket, significant changes are required. Furthermore, the client's update mechanism must be enhanced to support Next.js's multi-chunk HMR artifacts.

Proposed Architectural Changes

  1. Introduce a dedicated NextHmrConnector: This new class will orchestrate the entire process, managing the WebSocket connection and instructing the HMRClient when to apply updates.
  2. Modify the HMRClient for multi-chunk updates: The existing HMRUpdate type and applyUpdate method seem to handle only a single update script. This needs to be updated to handle a map of chunkId -> chunkScript.

Step 1: Handling Multi-Chunk Updates with Virtual Injection

Your universe HMR implementation already supports sophisticated in-memory chunk loading. The HMRUpdateData interface in your types already handles multi-chunk scenarios, but needs enhancement for virtual updates.

Current Architecture Analysis:

Your existing HMRUpdateData interface supports single script mode:

export interface HMRUpdateData {
  manifest: HMRManifest;
  script: string; // Single script approach
  originalInfo?: {
    updateId?: string;
    webpackHash?: string;
  };
}

Recommended Enhancement for Virtual Multi-Chunk Support:

Extend HMRUpdateData to support both single script and multi-chunk virtual modes:

// Enhanced HMRUpdateData interface
export interface HMRUpdateData {
  manifest: HMRManifest;
  script?: string; // Legacy single script support
  chunks?: { [chunkId: string]: string }; // Virtual multi-chunk support
  originalInfo?: {
    updateId?: string;
    webpackHash?: string;
    isVirtualUpdate?: boolean; // Flag for virtual updates
  };
}

Updated HMRClient.applyUpdate method:

async applyUpdate(updateData: HMRUpdate): Promise<CheckResult> {
    try {
      // ...existing validation...

      const update = updateData.update;
      if (!update) {
        throw new Error('Update data is null');
      }
      
      const manifestJsonString = JSON.stringify(update.manifest);
      
      // Enhanced chunk preparation supporting virtual updates
      let chunkJsStringsMap: { [chunkId: string]: string };
      
      if (update.chunks) {
        // Use virtual multi-chunk data directly
        chunkJsStringsMap = update.chunks;
      } else {
        // Fallback to single script mode (backward compatibility)
        chunkJsStringsMap = this.prepareChunkMap(update);
      }

      await applyHotUpdateFromStringsByPatching(
        module!,
        typeof __webpack_require__ !== 'undefined' ? __webpack_require__ : null,
        manifestJsonString,
        chunkJsStringsMap,
      );

      // ...rest of the method...
    } catch (error) {
      // ...error handling...
    }
}

Step 2: Virtual Update Integration (No Fetch Required)

Your universe HMR system has sophisticated in-memory chunk loading capabilities that can completely bypass the traditional fetch-based approach. Here's how to integrate virtual updates:

Enhanced Virtual NextHmrConnector:

// integration/virtual-next-hmr-connector.ts

import { HMRClient, HMRUpdate } from './hmr-client';

export class VirtualNextHmrConnector {
    private client: HMRClient;
    private ws: WebSocket | null = null;
    private currentHash: string | null = null;
    private sessionId: string | null = null;  // Track server sessions
    private virtualUpdates: Map<string, { manifest: any, chunks: { [chunkId: string]: string } }> = new Map();

    constructor() {
        this.client = new HMRClient({ autoAttach: false });
    }

    // NEW: Register virtual updates before they're needed
    public registerVirtualUpdate(hash: string, manifest: any, chunks: { [chunkId: string]: string }) {
        this.virtualUpdates.set(hash, { manifest, chunks });
        console.log(`[Virtual HMR] Registered virtual update for hash: ${hash}`);
    }

    // NEW: Create and inject virtual updates programmatically
    public async createVirtualUpdate(chunkId: string, moduleCode: string): Promise<string> {
        const virtualHash = `virtual-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
        
        const virtualManifest = {
            h: virtualHash,
            c: [chunkId],  // Next.js expects array format for chunks
            r: [],         // Removed chunks (array)
            m: [`./${chunkId}.js`]  // Updated modules (array)
        };

        const virtualChunks = {
            [chunkId]: moduleCode
        };

        this.registerVirtualUpdate(virtualHash, virtualManifest, virtualChunks);
        
        // Apply the virtual update immediately
        await this.processVirtualUpdate(virtualHash);
        
        return virtualHash;
    }

    public connect() {
        if (typeof window === 'undefined') return;

        this.client.attach();

        const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
        const host = window.location.host;
        this.ws = new WebSocket(`${protocol}://${host}/_next/webpack-hmr`);

        this.ws.onmessage = this.handleMessage.bind(this);
        this.ws.onclose = () => console.log('[Virtual HMR] WebSocket closed.');
    }

    private async handleMessage(event: MessageEvent) {
        const message = JSON.parse(event.data);

        // Handle session ID for server restart detection (matches Next.js internal behavior)
        if (message.sessionId && this.sessionId && message.sessionId !== this.sessionId) {
            console.log('[Virtual HMR] Server restarted, reloading page');
            window.location.reload();
            return;
        }
        if (message.sessionId) {
            this.sessionId = message.sessionId;
        }

        switch (message.action) {
            case 'sync':
            case 'built':
                this.currentHash = message.hash;
                
                // Check if we have a virtual update for this hash
                if (this.virtualUpdates.has(message.hash)) {
                    console.log(`[Virtual HMR] Using virtual update for hash: ${message.hash}`);
                    await this.processVirtualUpdate(message.hash);
                } else {
                    console.log(`[Virtual HMR] No virtual update found, falling back to fetch for: ${message.hash}`);
                    setTimeout(() => this.processUpdate(message.hash), 0);
                }
                break;

            case 'serverComponentChanges':
                console.log('[Virtual HMR] Server component changes detected - reloading');
                window.location.reload();
                break;

            case 'reloadPage':
                console.log('[Virtual HMR] Full page reload requested');
                window.location.reload();
                break;
        }
    }

    // NEW: Process virtual updates without any network requests
    private async processVirtualUpdate(hash: string): Promise<void> {
        const virtualUpdate = this.virtualUpdates.get(hash);
        if (!virtualUpdate) {
            console.error(`[Virtual HMR] Virtual update not found for hash: ${hash}`);
            return;
        }

        try {
            console.log(`[Virtual HMR] Processing virtual update: ${hash}`);
            
            // Create update payload using virtual data
            const updatePayload: HMRUpdate = {
                update: {
                    manifest: virtualUpdate.manifest,
                    chunks: virtualUpdate.chunks,
                    originalInfo: { 
                        updateId: hash, 
                        webpackHash: hash,
                        isVirtualUpdate: true 
                    }
                }
            };

            // Apply the virtual update
            const result = await this.client.applyUpdate(updatePayload);

            if (result.success) {
                console.log(`[Virtual HMR] Virtual update ${hash} applied successfully`);
                // Clean up used virtual update
                this.virtualUpdates.delete(hash);
            } else {
                console.error(`[Virtual HMR] Failed to apply virtual update ${hash}:`, result.message);
            }

        } catch (error) {
            console.error('[Virtual HMR] Error processing virtual update:', error);
        }
    }

    // Fallback to traditional fetch-based updates
    private async processUpdate(hash: string) {
        if (hash !== this.currentHash) return;

        try {
            const manifestResponse = await fetch(`/_next/static/webpack/${hash}.webpack.hot-update.json`);
            const manifest = await manifestResponse.json();

            const chunkIds = manifest.c || [];
            const chunkFetchPromises = chunkIds.map(async (chunkId: string) => {
                const scriptResponse = await fetch(`/_next/static/webpack/webpack.${hash}.hot-update.js`);
                const scriptContent = await scriptResponse.text();
                return { chunkId, scriptContent };
            });
            
            const fetchedChunks = await Promise.all(chunkFetchPromises);
            const chunksMap: { [chunkId: string]: string } = {};
            
            for (const { chunkId, scriptContent } of fetchedChunks) {
                chunksMap[chunkId] = scriptContent;
            }

            const updatePayload: HMRUpdate = {
                update: {
                    manifest: manifest,
                    chunks: chunksMap,
                    originalInfo: { updateId: hash, webpackHash: hash }
                }
            };

            const result = await this.client.applyUpdate(updatePayload);

            if (result.success) {
                console.log(`[Virtual HMR] Traditional update ${hash} applied successfully`);
            } else {
                console.error(`[Virtual HMR] Failed to apply traditional update ${hash}:`, result.message);
                window.location.reload();
            }

        } catch (error) {
            console.error('[Virtual HMR] Error processing traditional update:', error);
            window.location.reload();
        }
    }
}

Example Usage - Creating Virtual Updates:

// Example: Virtual _document.js update without any files
const connector = new VirtualNextHmrConnector();

// Create a virtual _document.js update
const virtualDocumentCode = `
"use strict";
exports.id = "pages/_document";
exports.modules = {
  "./pages/_document.js": (module, __webpack_exports__, __webpack_require__) => {
    __webpack_require__.r(__webpack_exports__);
    
    const Document = __webpack_require__("next/document");
    
    class VirtualDocument extends Document.default {
      static async getInitialProps(ctx) {
        const initialProps = await Document.default.getInitialProps(ctx);
        return {
          ...initialProps,
          virtualUpdate: true,
          timestamp: Date.now()
        };
      }
      
      render() {
        return Document.createElement(Document.Html, null, [
          Document.createElement(Document.Head, { key: 'head' }),
          Document.createElement('body', { key: 'body' }, [
            Document.createElement(Document.Main, { key: 'main' }),
            Document.createElement(Document.NextScript, { key: 'script' })
          ])
        ]);
      }
    }
    
    __webpack_exports__.default = VirtualDocument;
  }
};

exports.runtime = function(__webpack_require__) {
  console.log('[Virtual HMR] Virtual _document runtime executed');
};
`;

// Apply the virtual update immediately
const virtualHash = await connector.createVirtualUpdate("pages/_document", virtualDocumentCode);
console.log(`Virtual update created with hash: ${virtualHash}`);

Step 3: Implementing the Traditional NextHmrConnector

For compatibility with standard Next.js HMR, here's the traditional fetch-based connector:

// integration/next-hmr-connector.ts

import { HMRClient, HMRUpdate } from './hmr-client'; // Adjust path

export class NextHmrConnector {
    private client: HMRClient;
    private ws: WebSocket | null = null;
    private currentHash: string | null = null;

    constructor() {
        // Initialize your HMR client, but disable auto-attach
        this.client = new HMRClient({ autoAttach: false });
    }

    public connect() {
        // Ensure we are in a browser environment
        if (typeof window === 'undefined') return;

        this.client.attach();

        const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
        const host = window.location.host;
        this.ws = new WebSocket(`${protocol}://${host}/_next/webpack-hmr`);

        this.ws.onmessage = this.handleMessage.bind(this);

        this.ws.onclose = () => {
            console.log('[HMR Connector] WebSocket closed.');
        };
    }

    private async handleMessage(event: MessageEvent) {
        const message = JSON.parse(event.data);

        switch (message.action) {
            case 'sync':
            case 'built':
                this.currentHash = message.hash;
                // Defer processing to allow other messages to arrive
                setTimeout(() => this.processUpdate(message.hash), 0);
                break;

            case 'serverComponentChanges':
                // Server components cannot be hot-reloaded.
                // A full reload is the safest approach. A more advanced
                // implementation could try to use Next's router for a soft refresh.
                window.location.reload();
                break;

            case 'reloadPage':
                window.location.reload();
                break;
        }
    }

    private async processUpdate(hash: string) {
        if (hash !== this.currentHash) {
            // A newer update has already arrived, ignore this one.
            return;
        }

        try {
            // 1. Fetch the HMR manifest
            const manifestResponse = await fetch(`/_next/static/webpack/${hash}.webpack.hot-update.json`);
            const manifest = await manifestResponse.json();

            // 2. Fetch all updated chunk scripts
            const chunkIds = manifest.c || [];
            const chunkFetchPromises = chunkIds.map(async (chunkId: string) => {
                const scriptResponse = await fetch(`/_next/static/webpack/webpack.${hash}.hot-update.js`);
                const scriptContent = await scriptResponse.text();
                return { chunkId, scriptContent };
            });
            
            const fetchedChunks = await Promise.all(chunkFetchPromises);

            const chunksMap: { [chunkId: string]: string } = {};
            for (const { chunkId, scriptContent } of fetchedChunks) {
                chunksMap[chunkId] = scriptContent;
            }

            // 3. Prepare the update payload for your HMRClient
            const updatePayload: HMRUpdate = {
                update: {
                    manifest: manifest,
                    chunks: chunksMap,
                    originalInfo: { updateId: hash, webpackHash: hash }
                }
            };

            // 4. Apply the update
            const result = await this.client.applyUpdate(updatePayload);

            if (result.success) {
                console.log(`[HMR Connector] Update ${hash} applied successfully.`);
            } else {
                console.error(`[HMR Connector] Failed to apply update ${hash}:`, result.message);
                // If HMR fails, a reload is often necessary to get the app back in a consistent state.
                window.location.reload();
            }

        } catch (error) {
            console.error('[HMR Connector] Error processing update:', error);
            window.location.reload(); // Reload on error to be safe
        }
    }
}

Step 3: Initializing the Connector in Your Application

The NextHmrConnector must be initialized early in your application's client-side startup process. In a Next.js app, a good place for this is in your custom _app.tsx file.

// pages/_app.tsx or a similar client-side entry point

import { useEffect } from 'react';
import { NextHmrConnector } from '../integration/next-hmr-connector';

// Only initialize in development mode
if (process.env.NODE_ENV === 'development') {
    const connector = new NextHmrConnector();
    connector.connect();
}

function MyApp({ Component, pageProps }) {
  // ... your app logic
  return <Component {...pageProps} />;
}

export default MyApp;

3. Summary of Integration Flow

sequenceDiagram
    participant ClientApp as Client Application
    participant Connector as NextHmrConnector
    participant HMRClient as Your HMRClient
    participant NextServer as Next.js Dev Server

    ClientApp->>Connector: new NextHmrConnector()
    ClientApp->>Connector: connect()
    Connector->>NextServer: WebSocket Handshake to /_next/webpack-hmr
    NextServer-->>Connector: WebSocket Connection Established

    Note over NextServer: User saves a file...
    NextServer->>NextServer: Compiles changes...
    
    NextServer-->>Connector: Sends { action: 'built', hash: '...' }

    Connector->>Connector: handleMessage('built')
    Connector->>NextServer: fetch manifest (/_next/.../{hash}.hot-update.json)
    NextServer-->>Connector: Returns Manifest JSON

    Connector->>NextServer: fetch chunk scripts (/_next/.../{chunkId}.{hash}.hot-update.js)
    NextServer-->>Connector: Returns Chunk Scripts

    Connector->>HMRClient: applyUpdate({ manifest, chunks: { ... } })
    HMRClient->>HMRClient: applyHotUpdateFromStringsByPatching()
    Note right of HMRClient: Patches webpack runtime<br>and applies update.

    HMRClient-->>Connector: Returns { success: true }
    
    Note over ClientApp: UI updates without a page reload.
Loading

By following this guide, you can create a robust bridge between your custom HMR client and the Next.js development environment, enabling advanced, in-memory hot reloading while respecting the specific requirements of the Next.js architecture.

4. Advanced Technique: Programmatic Server-Side Entry Point HMR

Beyond responding to file-system-based changes, your HMR client can be used to programmatically trigger in-memory HMR updates for server-side webpack entry points. This technique focuses specifically on the server-compiled modules that Next.js manages through its multi-compiler system.

⚠️ Server-Side Scope Only: This section addresses server-side entry point hot reloading only. This is fundamentally different from client-side HMR and operates on the compiled server bundle entry points.

Understanding Server-Side Entry Points

Based on the Next.js architecture analysis, the server-side entry points that can be programmatically hot-reloaded are:

  1. Server Page Entries: Compiled server-side page modules (pages/*.js entries)
  2. App Router Entries: Server-side App Router pages (app/* entries)
  3. Webpack Runtime Modules: webpack-runtime.js and webpack-api-runtime.js
  4. Server Component Modules: React Server Component compiled outputs

These entry points are managed by the NextJsRequireCacheHotReloader plugin and the OnDemandEntryHandler.

Server-Side Entry Point Architecture

graph TB
    subgraph "Server Webpack Compilation"
        ServerCompiler[Server Compiler]
        EntryManager[On-Demand Entry Handler]
        CacheManager[Require Cache Hot Reloader]
    end
    
    subgraph "Server Entry Points"
        PagesEntries["pages/*.js entries<br/>(Server-compiled page modules)"]
        AppEntries["app/* entries<br/>(App Router server modules)"]
        RuntimeEntries["webpack-runtime.js<br/>webpack-api-runtime.js"]
        ServerComponents["Server Component modules"]
    end
    
    subgraph "Server Runtime Environment"
        NodeRequireCache["Node.js require.cache"]
        EdgeModuleContext["Edge Runtime Module Context"]
        ServerBundleCache[".next/server/ compiled outputs"]
    end
    
    ServerCompiler --> PagesEntries
    ServerCompiler --> AppEntries
    ServerCompiler --> RuntimeEntries
    ServerCompiler --> ServerComponents
    
    EntryManager --> PagesEntries
    EntryManager --> AppEntries
    
    CacheManager --> NodeRequireCache
    CacheManager --> EdgeModuleContext
    CacheManager --> ServerBundleCache
    
    PagesEntries --> ServerBundleCache
    AppEntries --> ServerBundleCache
    RuntimeEntries --> ServerBundleCache
    ServerComponents --> ServerBundleCache
Loading

Server-Side HMR Flow for Entry Points

sequenceDiagram
    participant ServerCode as Server-Side Code
    participant HMRClient as Your HMRClient
    participant ServerWebpack as Server Webpack Runtime
    participant RequireCache as Node.js require.cache
    participant NextServer as Next.js Server Process
    
    Note over ServerCode: Target: Server-compiled entry points only
    
    ServerCode->>ServerCode: 1. Construct Server Entry Manifest
    ServerCode->>ServerCode: 2. Construct Server Entry Update Chunk
    
    ServerCode->>HMRClient: 3. new HMRClient()
    
    ServerCode->>HMRClient: 4. forceUpdate({ serverEntryData })
    HMRClient->>HMRClient: applyUpdate(serverEntryData)
    
    HMRClient->>ServerWebpack: 5. Apply to server webpack runtime
    ServerWebpack->>ServerWebpack: 6. Update server entry modules
    
    Note over ServerCode: Manual cache invalidation required
    ServerCode->>RequireCache: 7. deleteCache(entryPath)
    ServerCode->>RequireCache: 8. clearModuleContext(entryPath)
    
    RequireCache->>NextServer: 9. Force re-require on next request
    
    Note over NextServer: Server entry reloaded on next HTTP request
Loading

Correct Use Case: Server Entry Point Updates

Your HMR client can programmatically update server-compiled entry points:

// Server-side programmatic entry point hot reload
import { HMRClient } from '@module-federation/node/utils/hmr-client';
import { deleteCache } from 'next/dist/server/dev/require-cache';
import { clearModuleContext } from 'next/dist/server/web/sandbox/context';
import path from 'path';

async function updateServerEntry(entryName) {
  const hmrClient = new HMRClient({ logging: true });

  // Target a specific server-compiled entry point
  const serverEntryUpdateChunk = `
    exports.modules = {
      './pages/${entryName}.js': function(module, exports, __webpack_require__) {
        // Updated server-side page module
        console.log('[Server HMR] Updated server entry: ${entryName}');
        
        // Your updated server-side page logic here
        function getServerSideProps(context) {
          return {
            props: {
              timestamp: Date.now(),
              hmrUpdate: true,
              entryName: '${entryName}'
            }
          };
        }
        
        function PageComponent(props) {
          // Server-side component logic
          return {
            type: 'div',
            props: {
              children: 'Updated server entry: ' + props.entryName + ' at ' + props.timestamp
            }
          };
        }
        
        module.exports = PageComponent;
        module.exports.getServerSideProps = getServerSideProps;
        exports.default = PageComponent;
      }
    };
    
    exports.runtime = function(__webpack_require__) {
      console.log('[Server HMR] Server entry runtime executed for: ${entryName}');
    };
  `;

  const serverEntryManifest = {
    h: 'server-entry-' + entryName + '-' + Date.now(),
    c: [`pages/${entryName}`],  // Server entry chunk
    r: [],
    m: [`./pages/${entryName}.js`]  // Server entry module
  };

  const result = await hmrClient.forceUpdate({
    createMinimalUpdate: false,
    updateData: {
      update: {
        manifest: serverEntryManifest,
        chunks: { [`pages/${entryName}`]: serverEntryUpdateChunk },
        originalInfo: {
          updateId: `server-entry-${entryName}-` + Date.now(),
          webpackHash: serverEntryManifest.h
        }
      }
    }
  });

  console.log('Server entry update result:', result);

  // Critical: Clear server-side caches for the updated entry
  if (result.success) {
    await clearServerEntryCache(entryName);
  }

  return result;
}

async function clearServerEntryCache(entryName) {
  console.log('[Server HMR] Clearing server cache for entry:', entryName);

  const buildOutputPath = path.resolve(process.cwd(), '.next/server');
  
  // Clear the specific server entry from cache
  const serverEntryPath = path.join(buildOutputPath, `pages/${entryName}.js`);
  
  // Clear Edge runtime context (if applicable)
  clearModuleContext(serverEntryPath);
  
  // Clear Node.js require cache
  deleteCache(serverEntryPath);
  
  // Also clear webpack runtime caches to ensure consistency
  const runtimePaths = [
    path.join(buildOutputPath, 'webpack-runtime.js'),
    path.join(buildOutputPath, 'webpack-api-runtime.js')
  ];
  
  for (const runtimePath of runtimePaths) {
    deleteCache(runtimePath);
  }

  console.log('[Server HMR] Server entry cache cleared successfully');
}

// Usage: Update a specific server entry point
// updateServerEntry('about');  // Updates pages/about.js server entry
// updateServerEntry('api/users');  // Updates pages/api/users.js server entry

Server Entry Point Integration in _document.js

For integration within a Next.js _document.js file, focus on server-side entry management:

// In pages/_document.js - Server-side only
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);

    // Server-side entry point management
    if (process.env.NODE_ENV === 'development') {
      // Only run server-side entry HMR logic during development
      try {
        await checkAndUpdateServerEntries(ctx.pathname);
      } catch (error) {
        console.error('[Server HMR] Entry update failed:', error);
      }
    }

    return initialProps;
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

async function checkAndUpdateServerEntries(pathname) {
  // Example: Programmatically refresh specific server entries
  if (pathname && pathname.startsWith('/api/')) {
    const apiRoute = pathname.replace('/api/', '');
    console.log('[Server HMR] Checking API route entry:', apiRoute);
    
    // Use your HMR client to update the specific API route entry
    // This operates on the server-compiled API route module
    await updateServerEntry(`api/${apiRoute}`);
  }
}

export default MyDocument;

Server Entry Point Limitations

  1. Real File Requirement: Cache invalidation only works for entries that exist as real files in .next/server/
  2. Server Restart: Some server entry updates may require a server restart for full effect
  3. Build Dependency: Server entries must be part of the webpack compilation output
  4. Runtime Context: Edge runtime entries have different cache invalidation requirements

Summary: Server-Side Entry Point HMR

Server Page Entries: Update compiled server-side page modules (pages/*.js) ✅ App Router Server Entries: Update server-side App Router modules (app/*)
Webpack Runtime Modules: Update server webpack runtime ✅ Cache Invalidation: Clear require.cache and Edge contexts for real files ⚠️ Server-Side Only: This technique applies exclusively to server-compiled entry points ⚠️ Build Output Dependency: Only works with actual webpack compilation outputs

This approach provides precise control over server-side entry point hot reloading while respecting Next.js's server compilation architecture.

5. Programmatic Server "Soft Reboot" via HMR

Beyond individual entry point updates, you can use HMR mechanisms to trigger a complete Next.js server "soft reboot" that mimics a fresh server start while preserving the development session.

Understanding Next.js Server Soft Reboot

A "soft reboot" differs from restarting the development server process:

  • Hard Restart: Kill and restart the next dev process
  • Soft Reboot: Reset server state while keeping the process and WebSocket connections alive

Triggering Soft Reboot via HMR Actions

// Programmatic soft reboot using Next.js HMR infrastructure
import { HMRClient } from '@module-federation/node/utils/hmr-client';

class NextServerSoftReboot {
  constructor() {
    this.hmrClient = new HMRClient({ logging: true });
  }

  async triggerSoftReboot() {
    // 1. Send middleware changes to trigger reinitialization
    await this.sendHMRAction('middlewareChanges', {});
    
    // 2. Clear all server-side caches
    await this.clearAllServerCaches();
    
    // 3. Trigger server component refresh
    await this.sendHMRAction('serverComponentChanges', { 
      hash: 'soft-reboot-' + Date.now() 
    });
    
    console.log('[Soft Reboot] Server state reset completed');
  }

  async sendHMRAction(action, data) {
    // Connect to Next.js WebSocket to send HMR actions
    const protocol = process.env.HTTPS ? 'wss' : 'ws';
    const host = process.env.HOST || 'localhost';
    const port = process.env.PORT || 3000;
    
    const ws = new WebSocket(`${protocol}://${host}:${port}/_next/webpack-hmr`);
    
    return new Promise((resolve, reject) => {
      ws.onopen = () => {
        ws.send(JSON.stringify({ action, ...data }));
        ws.close();
        resolve();
      };
      
      ws.onerror = reject;
      ws.onclose = resolve;
    });
  }

  async clearAllServerCaches() {
    const { deleteCache } = await import('next/dist/server/dev/require-cache');
    const { clearModuleContext } = await import('next/dist/server/web/sandbox/context');
    const path = await import('path');
    const fs = await import('fs');

    // Clear all compiled server entries
    const serverDir = path.resolve(process.cwd(), '.next/server');
    
    if (fs.existsSync(serverDir)) {
      const clearDirectory = (dir) => {
        const entries = fs.readdirSync(dir, { withFileTypes: true });
        
        for (const entry of entries) {
          const fullPath = path.join(dir, entry.name);
          
          if (entry.isDirectory()) {
            clearDirectory(fullPath);
          } else if (entry.name.endsWith('.js')) {
            deleteCache(fullPath);
            clearModuleContext(fullPath);
          }
        }
      };
      
      clearDirectory(serverDir);
    }

    console.log('[Soft Reboot] All server caches cleared');
  }
}

// Usage: Trigger programmatic soft reboot
// const rebooter = new NextServerSoftReboot();
// await rebooter.triggerSoftReboot();

Specific _document.js HMR Integration

For _document.js HMR integration with chunk ID "pages/_document":

// _document.js HMR integration with verified chunk ID
async function updateDocumentEntry() {
  const hmrClient = new HMRClient({ logging: true });

  // Confirmed chunk ID from Next.js source analysis
  const DOCUMENT_CHUNK_ID = "pages/_document";

  const documentUpdateChunk = `
    exports.modules = {
      './pages/_document.js': function(module, exports, __webpack_require__) {
        // Updated _document implementation
        const Document = __webpack_require__('next/document');
        const { Html, Head, Main, NextScript } = Document;
        
        class MyDocument extends Document.default {
          static async getInitialProps(ctx) {
            const initialProps = await Document.default.getInitialProps(ctx);
            
            // Custom server-side logic during HMR
            console.log('[Document HMR] Updated at:', new Date().toISOString());
            
            return {
              ...initialProps,
              hmrTimestamp: Date.now()
            };
          }
          
          render() {
            return new Html({
              children: [
                new Head({}),
                new Document.body({
                  children: [
                    new Main({}),
                    new NextScript({})
                  ]
                })
              ]
            });
          }
        }
        
        module.exports = MyDocument;
        exports.default = MyDocument;
      }
    };
    
    exports.runtime = function(__webpack_require__) {
      console.log('[Document HMR] Runtime executed for pages/_document');
    };
  `;

  const documentManifest = {
    h: 'document-hmr-' + Date.now(),
    c: [DOCUMENT_CHUNK_ID],  // Verified chunk ID
    r: [],
    m: ['./pages/_document.js']
  };

  // Apply the update to server webpack runtime
  const result = await hmrClient.forceUpdate({
    createMinimalUpdate: false,
    updateData: {
      update: {
        manifest: documentManifest,
        chunks: { [DOCUMENT_CHUNK_ID]: documentUpdateChunk },
        originalInfo: {
          updateId: `document-hmr-${Date.now()}`,
          webpackHash: documentManifest.h
        }
      }
    }
  });

  if (result.success) {
    console.log('[Document HMR] Update applied successfully');
    
    // Note: _document changes require full page reload
    // Next.js will automatically send RELOAD_PAGE action
    console.log('[Document HMR] Full page reload will be triggered');
  } else {
    console.error('[Document HMR] Update failed:', result.message);
  }

  return result;
}

// Usage for _document.js HMR
// await updateDocumentEntry();

Server Reboot vs Entry Point Updates

Technique Use Case Scope WebSocket Connection
Soft Reboot Complete server state reset All entries and caches Preserved
Entry Point HMR Specific module updates Single entry point Preserved
Hard Restart Development server restart Complete process Lost

Important Considerations

  1. _document.js Limitations: Always triggers full page reload due to HTML structure changes
  2. Cache Coordination: Server cache clearing must precede HMR manifest application
  3. WebSocket Preservation: Soft reboot maintains development session connectivity
  4. Chunk ID Accuracy: Use verified chunk IDs ("pages/_document", not "_document")
  5. Build Dependency: Only works with webpack-compiled server bundles in .next/server/

This comprehensive approach enables sophisticated server-side development workflows while respecting Next.js's HMR architecture and limitations.

6. Virtual Update Summary and Best Practices

Key Capabilities Enabled

Your universe HMR system now supports:

  1. 🚀 Virtual Updates Without Fetch: Complete bypass of network requests for HMR updates
  2. 📦 Multi-Chunk Support: Handle complex webpack chunk scenarios seamlessly
  3. 🔄 In-Memory Chunk Loading: Use the sophisticated inMemoryChunks and manifestRef system
  4. ⚡ Programmatic Control: Create and inject updates on-demand without file system dependencies
  5. 🎯 Next.js Integration: Full compatibility with Next.js WebSocket HMR protocol

Virtual Update Flow

sequenceDiagram
    participant App as Your Application
    participant Connector as VirtualNextHmrConnector
    participant HMRClient as Universe HMRClient
    participant Runtime as Webpack Runtime
    
    App->>Connector: createVirtualUpdate(chunkId, moduleCode)
    Connector->>Connector: Generate virtual hash + manifest
    Connector->>Connector: registerVirtualUpdate(hash, manifest, chunks)
    Connector->>HMRClient: applyUpdate({ chunks: virtualChunks })
    HMRClient->>Runtime: setInMemoryChunk() + setInMemoryManifest()
    Runtime->>Runtime: Apply virtual update via vm.runInThisContext()
    Runtime->>App: Module updated without any network requests
Loading

Usage Patterns

Pattern 1: Preemptive Virtual Updates

// Register virtual updates before Next.js sends the WebSocket message
connector.registerVirtualUpdate("abc123", manifest, chunks);
// When Next.js sends { action: 'built', hash: 'abc123' }, 
// your virtual update is used instead of fetch

Pattern 2: On-Demand Virtual Updates

// Create and apply virtual updates immediately
const hash = await connector.createVirtualUpdate("pages/home", moduleCode);

Pattern 3: Hybrid Mode

// Virtual updates for specific chunks, fallback to fetch for others
if (this.virtualUpdates.has(message.hash)) {
    await this.processVirtualUpdate(message.hash);
} else {
    await this.processUpdate(message.hash); // Traditional fetch
}

Performance Benefits

  • Zero Network Latency: Virtual updates apply instantly
  • Reduced Server Load: No file system reads or HTTP requests
  • Enhanced Development Speed: Immediate feedback cycles
  • Memory Efficiency: In-memory chunk storage with cleanup

Integration with Module Federation

Your virtual HMR system is particularly powerful for Module Federation scenarios where:

  • Remote modules need hot updates without network dependencies
  • Federation remotes want to provide their own update mechanisms
  • Cross-application HMR coordination is required
  • Development environments need isolated update control

This advanced virtual update capability transforms your HMR client into a sophisticated development tool that can operate independently of traditional file-based workflows while maintaining full compatibility with Next.js development patterns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment