From dc2c208ca62679634aa7c4da667ca284ccde9441 Mon Sep 17 00:00:00 2001 From: userquin Date: Thu, 10 Sep 2026 20:27:39 +0200 Subject: [PATCH 1/2] fix(dev): quit command hangs/timeouts on Windows --- packages/nuxt-cli/src/dev/utils.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/nuxt-cli/src/dev/utils.ts b/packages/nuxt-cli/src/dev/utils.ts index a6787e819..726011876 100644 --- a/packages/nuxt-cli/src/dev/utils.ts +++ b/packages/nuxt-cli/src/dev/utils.ts @@ -1129,15 +1129,16 @@ export class NuxtDevServer extends EventEmitter { const baseURL = nuxt.options.app.baseURL.startsWith('./') ? nuxt.options.app.baseURL.slice(1) : nuxt.options.app.baseURL const assetsDir = nuxt.options.app.buildAssetsDir const viteHmrPath = `${baseURL.replace(/\/$/, '')}/${assetsDir.replace(/^\//, '')}` - if (req.url?.startsWith(viteHmrPath)) { - return // Skip for Vite HMR - } - nuxt.server.upgrade(req, socket as any, head) - + // fix #1531: track all opened connections + // quit command hangs/timeouts on Windows 10 with Vite HMR this.#websocketConnections.add(socket) socket.on('close', () => { this.#websocketConnections.delete(socket) }) + if (req.url?.startsWith(viteHmrPath)) { + return // Skip for Vite HMR + } + nuxt.server.upgrade(req, socket as any, head) }) } From 3cc3ae63292a00a4b55f0e8b7a329e571bf7aeb6 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sat, 26 Sep 2026 11:58:19 +0000 Subject: [PATCH 2/2] fix(dev): close websocket connections before closing nuxt --- packages/nuxt-cli/src/dev/utils.ts | 3 +- .../nuxt-cli/test/unit/dev/lifecycle.spec.ts | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/nuxt-cli/src/dev/utils.ts b/packages/nuxt-cli/src/dev/utils.ts index 726011876..3b731f09f 100644 --- a/packages/nuxt-cli/src/dev/utils.ts +++ b/packages/nuxt-cli/src/dev/utils.ts @@ -1129,8 +1129,6 @@ export class NuxtDevServer extends EventEmitter { const baseURL = nuxt.options.app.baseURL.startsWith('./') ? nuxt.options.app.baseURL.slice(1) : nuxt.options.app.baseURL const assetsDir = nuxt.options.app.buildAssetsDir const viteHmrPath = `${baseURL.replace(/\/$/, '')}/${assetsDir.replace(/^\//, '')}` - // fix #1531: track all opened connections - // quit command hangs/timeouts on Windows 10 with Vite HMR this.#websocketConnections.add(socket) socket.on('close', () => { this.#websocketConnections.delete(socket) @@ -1231,6 +1229,7 @@ export class NuxtDevServer extends EventEmitter { } async close(): Promise { + this.#closeWebSocketConnections() if (this.#currentNuxt) { await this.#currentNuxt.close() } diff --git a/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts b/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts index a034fbd58..27d6ee5b5 100644 --- a/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts +++ b/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts @@ -1,8 +1,9 @@ -import type { AddressInfo } from 'node:net' +import type { AddressInfo, Socket } from 'node:net' import { EventEmitter } from 'node:events' import { existsSync, readFileSync } from 'node:fs' import { mkdtemp, rm } from 'node:fs/promises' +import { connect } from 'node:net' import { tmpdir } from 'node:os' import process from 'node:process' @@ -451,6 +452,32 @@ describe('dev server shutdown', () => { expect(existsSync(join(cwd, '.nuxt', 'nuxt.lock'))).toBe(false) }) + it('should drop open websocket connections before running nuxt close hooks', async () => { + const nuxt = createNuxt() + const upgraded = new Set() + Object.assign(nuxt.server, { + upgrade: (_req: unknown, socket: Socket) => { + upgraded.add(socket) + socket.once('close', () => upgraded.delete(socket)) + socket.write('HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n') + }, + }) + nuxt.hook('close', () => vi.waitFor(() => expect(upgraded.size).toBe(0), { timeout: 2000 })) + nuxt.close = () => nuxt.callHook('close') + loadNuxt.mockImplementation(() => Promise.resolve(nuxt)) + const server = createServer() + await server.init() + const { port } = server.listener.address as AddressInfo + + const client = connect(port, '127.0.0.1') + client.on('error', () => {}) + client.write('GET /_ws HTTP/1.1\r\nHost: 127.0.0.1\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n') + await vi.waitFor(() => expect(upgraded.size).toBe(1)) + + await expect(server.close()).resolves.toBeUndefined() + client.destroy() + }) + it('should stop answering once the listener is closed', async () => { const server = createServer() await server.init()