Repository: vercel/next.js
Pull request: Turbopack: Fix experimental server hmr for pages router

This is cherry-picked from #90389, which also serves as a test case for server hmr being enabled by default.

This fixes a number of issues with server hmr, most notably that pages router updates weren’t reflected:

1. **Pages Router pages weren't clearing the chunk cache** — the old condition (which just checked the cli flag) meant the chunk cache was only cleared when server HMR was entirely absent. Once server HMR was enabled, Pages Router pages (which don't use the Turbopack module registry) got stale.

2. **`deleteCache` was skipped for files with server HMR subscriptions** — this left stale entries in `evalManifest`'s `sharedCache`, causing "Could not find module in React Client Manifest" errors when new `'use client'` components were added.

3. **`clearModuleContext` was skipped for middleware and edge routes** — server HMR subscriptions include edge runtime chunks, but `__turbopack_server_hmr_apply__` can't reach those sandboxes. They need `clearModuleContext` called explicitly.

4. **Chunk cache wasn't cleared for `.env`/config changes** — when `force: true` (no code change), module-level env captures stayed stale. Now `__next__clear_chunk_cache__()` is called unconditionally when `force` is set.


Changed files:
filename: packages/next/src/server/dev/hot-reloader-turbopack.ts
patch: @@ -586,26 +586,37 @@ export async function createHotReloaderTurbopack(
       join(distDir, p)
     )
 
+    const { type: entryType } = splitEntryKey(key)
+    // Server HMR only applies to App Router Node.js runtime endpoints.
+    // Pages Router uses Node's require(), root entries (middleware/instrumentation)
+    // use the edge runtime, and App Router edge routes all don't support server HMR.
+    const usesServerHmr = entryType === 'app' && writtenEndpoint.type !== 'edge'
+
     for (const file of serverPaths) {
-      // Skip clearing cache for server chunks with active HMR subscriptions
-      // Server HMR already applied granular updates to the turbopack module cache
       const relativePath = relative(distDir, file)
-      if (serverHmrSubscriptions?.has(relativePath)) {
+
+      if (usesServerHmr && serverHmrSubscriptions?.has(relativePath)) {
+        // Skip deleteCache for server HMR module chunks.
+        // Pages Router entries are excluded by usesServerHmr (always false for
+        // pages), so they always get deleteCache regardless of subscriptions.
         continue
       }
 
       clearModuleContext(file)
+      // For Pages Router, edge routes, middleware, and manifest files
+      // (e.g., *_client-reference-manifest.js): clear the sharedCache in
+      // evalManifest(), Node.js require.cache, and edge runtime module contexts.
       deleteCache(file)
     }
 
-    // Clear Turbopack's module cache when server HMR is not active.
-    // When server HMR IS active, HMR manages the module cache granularly,
-    // so we must not clear it here.
-    // Not available in:
-    // - Pages Router (no server-side HMR)
-    // - Edge Runtime (uses browser runtime which already disposes chunks individually)
+    // Clear Turbopack's chunk-loading cache so chunks are re-required from disk on
+    // the next request.
+    //
+    // For App Router with server HMR, this is normally skipped as server HMR
+    // manages module updates in-place. However, it *is* required when force is `true`
+    // (like for .env file or tsconfig changes).
     if (
-      !serverHmrSubscriptions &&
+      (!usesServerHmr || force) &&
       typeof __next__clear_chunk_cache__ === 'function'
     ) {
       __next__clear_chunk_cache__()