#15427·react-router

`vite build` never exits in SPA mode with React Router 8

Author: nvignolaCreated Aug 21, 2026Updated Aug 27, 2026
Labelsbugpkg:@react-router/devfeat:vitefeat:spa-mode

Reproduction

https://github.com/nvignola/rr8-spa-build-hang

git clone https://github.com/nvignola/rr8-spa-build-hang
cd rr8-spa-build-hang
npm install
npx vite build

The repository is untouched create-react-router output with one line changed: ssr: truessr: false in react-router.config.ts.

To build it from scratch instead:

npx create-react-router@latest repro --yes --install --no-git-init
cd repro
# set `ssr: false` in react-router.config.ts
npx vite build

System Info

System:
    OS: macOS 26.6.1
    CPU: (18) arm64 Apple M5 Max
    Memory: 118.88 MB / 36.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.14.1
    npm: 11.11.0
  Browsers:
    Brave Browser: 150.1.92.140
    Chrome: 151.0.7922.170
    Firefox Developer Edition: 153.0
    Safari: 26.6
  npmPackages:
    @react-router/dev: ^8 => 8.3.0
    @react-router/node: ^8 => 8.3.0
    @react-router/serve: ^8 => 8.3.0
    react-router: ^8 => 8.3.0
    vite: ^8.0.3 => 8.2.2

Used Package Manager

npm

Expected Behavior

vite build writes the output and exits, as it does with ssr: true and as it did in React Router 7.18.2.

Actual Behavior

The build finishes in well under a second, prints its last line, and the process never exits:

✓ built in 28ms
SPA Mode: Generated build/client/index.html

Everything in build/ is complete and correct: client/index.html, client/assets/, and server/. Only the exit is missing.


Scope

Setup vite build
8.3.0, ssr: false never exits
8.0.0, ssr: false never exits
8.3.0, ssr: true exits 0
7.18.2, ssr: false exits 0

Unaffected by the TypeScript version and by other Vite plugins.

Why this may not have been reported before

react-router build hides it. The CLI exits explicitly once the command resolves, so open handles never matter: https://github.com/remix-run/react-router/blob/react-router%408.3.0/packages/react-router-dev/cli/index.ts#L6

The project template ships "build": "react-router build", so only the hang is met only by calling vite build directly. That is also why I could not use the integration/bug-report-test.ts template. The integration harness builds fixture apps by spawning react-router build: https://github.com/remix-run/react-router/blob/react-router%408.3.0/integration/helpers/vite.ts#L303

Where it looks like it comes from

SPA mode prerenders index.html behind a Vite preview server:

https://github.com/remix-run/react-router/blob/react-router%408.3.0/packages/react-router-dev/vite/plugins/prerender.ts#L552

which is closed in a finally:

https://github.com/remix-run/react-router/blob/react-router%408.3.0/packages/react-router-dev/vite/plugins/prerender.ts#L401

} finally {
  await previewServer.close();
}

Node's server.close() stops new connections but does not destroy idle keep-alive sockets, so the handle can outlive the call. The hang begins on the line right after the prerender message prints, which fits.