nitrojs / nitrojs/nitro

Editing server api route file causes dev server to hang forever (HMR bug)

Open
#4,359 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug v2
Dominant language
TypeScript
Stars
11.2k
Forks
899
Avg merge
2d 24m
Merged PRs (30d)
40

Description

Environment
╭── Nuxt project info (copied to clipboard) ───────────────────────────────────╮
│                                                                              │
│  Operating system      Ubuntu 20.04 Linux 5.15.0-179-generic                 │
│  CPU                   Intel(R) Core(TM) i5-10300H CPU @ 2.50GHz (8 cores)   │
│  Node.js version       v24.11.0                                              │
│  nuxt/cli version      3.35.2                                                │
│  Package manager       pnpm 10.31.0                                          │
│  Nuxt version          4.4.8                                                 │
│  Nitro version         2.13.4                                                │
│  Builder               vite 7.3.5                                            │
│  Config                app, compatibilityDate, devtools, modules             │
│  Modules               -                                                     │
╰──────────────────────────────────────────────────────────────────────────────╯
Reproduction

https://stackblitz.com/github/martinszeltins/nuxt-hmr-hang-bug

Describe the bug

Initially reported to Nuxt here - https://github.com/nuxt/nuxt/issues/35374, there is a bug that causes HMR to hang forever in loading state.

On first edit, it reloads and everything seems to be normal. Then on second edit + file save - the dev server totally hangs forever. I have attached a screencast to easily see the problem.

  1. Start the dev server and open the app at http://localhost:3000/
  2. Edit layers/common/server/api/user.ts and save it. Everything reloads as normal. ✅
  3. Edit the user.ts file one more time and save it. The dev server now hangs forever ❌ 😔

With the help of AI, I was able to get a fix and added failing tests:

In Nitropack’s DevServer, during HMR it was routing requests to a stale worker while building=true, then killing that worker on reload — leaving SSR requests hung.
During Nitro HMR, getWorker() could still hand requests to an old dev worker while building=true, and dev:reload then killed that worker mid-SSR — so the browser reload never finished.
The patch closes workers as soon as a rebuild starts and makes getWorker() wait for a fresh worker instead of reusing a stale one during building, with a 120s safety cap.

Here is a repo with the patch applied that seems to fix the issue:
https://github.com/martinszeltins/nuxt-nitro-hang-patch-fix

pnpm-workspace.yaml:

patchedDependencies:
  nitropack@2.13.4: patches/nitropack@2.13.4.patch

patches/nitropack@2.13.4.patch:

diff --git a/dist/core/index.mjs b/dist/core/index.mjs
index 8a601e0360c576d1e1c0e73da413d2393b8beb26..757aeea4e03af9a2180d9ec4f9239f9e31c104a1 100644
--- a/dist/core/index.mjs
+++ b/dist/core/index.mjs
@@ -2682,6 +2682,9 @@ class DevServer {
     nitro.hooks.hook("dev:start", () => {
       this.building = true;
       this.buildError = void 0;
+      for (const worker of this.workers) {
+        worker.close();
+      }
     });
     nitro.hooks.hook("dev:reload", () => {
       this.buildError = void 0;
@@ -2755,12 +2758,17 @@ class DevServer {
   async getWorker() {
     let retry = 0;
     const maxRetries = isTest || isCI ? 100 : 10;
+    const getWorkerStart = Date.now();
+    const maxBuildingWaitMs = 120_000;
     while (this.building || ++retry < maxRetries) {
+      if (Date.now() - getWorkerStart > maxBuildingWaitMs) {
+        return;
+      }
       if ((this.workers.length === 0 || this.buildError) && !this.building) {
         return;
       }
       const activeWorker = this.workers.find((w) => w.ready);
-      if (activeWorker) {
+      if (activeWorker && !this.building) {
         return activeWorker;
       }
       await new Promise((resolve2) => setTimeout(resolve2, 600));

HMR tests failing tests that show the hang bug:
https://github.com/martinszeltins/nitro/pull/1

Patch that makes the tests pass:

diff --git a/src/core/dev-server/server.ts b/src/core/dev-server/server.ts
index 3d46c8ac..7da6df9d 100644
--- a/src/core/dev-server/server.ts
+++ b/src/core/dev-server/server.ts
@@ -85,6 +85,9 @@ class DevServer {
     nitro.hooks.hook("dev:start", () => {
       this.building = true;
       this.buildError = undefined;
+      for (const worker of this.workers) {
+        worker.close();
+      }
     });
 
     nitro.hooks.hook("dev:reload", () => {
@@ -164,12 +167,17 @@ class DevServer {
   async getWorker() {
     let retry = 0;
     const maxRetries = isTest || isCI ? 100 : 10;
+    const getWorkerStart = Date.now();
+    const maxBuildingWaitMs = 120_000;
     while (this.building || ++retry < maxRetries) {
+      if (Date.now() - getWorkerStart > maxBuildingWaitMs) {
+        return;
+      }
       if ((this.workers.length === 0 || this.buildError) && !this.building) {
         return;
       }
       const activeWorker = this.workers.find((w) => w.ready);
-      if (activeWorker) {
+      if (activeWorker && !this.building) {
         return activeWorker;
       }
       await new Promise((resolve) => setTimeout(resolve, 600));

Screencast:

https://github.com/user-attachments/assets/966a7b43-fb47-416a-8d1a-babed415aa4d

Additional context

No response

Logs

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the two-save hang with the linked StackBlitz project, then inspect src/core/dev-server/server.ts and the HMR tests linked in the report. Trace the dev:start, dev:reload, and getWorker flow, and verify that repeated edits to layers/common/server/api/user.ts complete normally and the failing HMR tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript, vite
Domain
backend, developer-experience, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.