TanStack / TanStack/router

[Start] Client abort mid-SSR kills `vite dev` 120s later with @tanstack/nitro-v2-vite-plugin (unhandled 'error' from the stream-lifetime watchdog)

Open
#7,748 0 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Which project does this relate to?

Start

Describe the bug

With @tanstack/nitro-v2-vite-plugin on Vite 6, a client that aborts a request while SSR is still streaming leaves router-core's SSR transform stream uncleaned: the abort never propagates to the transform's cleanup, so serialization never finishes. 120 seconds later the stream-lifetime watchdog in transformStreamWithRouter calls controller.error(...) — but by then the adapting node Readable has no consumer and no 'error' listener, so the entire dev process exits with an unhandled 'error' event:

SSR stream transform exceeded maximum lifetime (120000ms), forcing cleanup
node:events:485
      throw er; // Unhandled 'error' event
      ^
Error: Stream lifetime exceeded
    at Timeout.<anonymous> (.../@tanstack/router-core/dist/esm/ssr/transformStreamWithRouter.js:478:32)
Emitted 'error' event on Readable instance at:
    at emitErrorNT (node:internal/streams/destroy:170:8)
error: script "dev" exited with code 1

This bites constantly in real usage: defaultPreload: "intent" aborts hover preloads all the time, so on any app with slow-ish SSR the dev server dies ~2 minutes after ordinary navigation.

What I've isolated:

  • A fresh create-start-app scaffold (Vite 8 + the new nitro/vite plugin, router-core 1.171.14) does not reproduce — the abort propagates, the transform is cleaned up immediately, and the watchdog never fires.
  • The watchdog code in transformStreamWithRouter is identical between router-core 1.171.13 and 1.171.14, so the difference is the dev-server adapter chain: the abort→cleanup propagation hole is specific to the @tanstack/nitro-v2-vite-plugin pipeline.
  • The production build (node .output/server/index.mjs, node-server preset) tolerates the same error — this is dev-only.

Two possible fixes, not mutually exclusive: propagate request aborts to the transform-stream cleanup in the nitro-v2 dev pipeline, and/or harden the watchdog in router-core to not error() a stream that no longer has a consumer (which would contain the blast radius for any adapter that misses cancellation).

Complete minimal reproducer

https://github.com/jayscambler/tanstack-start-ssr-abort-repro

(Three tiny routes; /slow has a 3s loader so a curl abort reliably lands mid-SSR.)

Steps to Reproduce the Bug
  1. bun install && bun run dev (vite dev, port 3100)
  2. curl -s --max-time 0.5 http://localhost:3100/slow — aborts while the 3s loader is still running
  3. Wait ~150 seconds
  4. curl http://localhost:3100/ — the dev process has exited with code 1 (crash above in the terminal)
Expected behavior

An aborted request cleans up its SSR transform stream (as the Vite 8 + nitro/vite pipeline does), and in no case should a watchdog firing on an abandoned stream take down the whole dev server.

Screenshots or Videos

No response

Platform
  • OS: macOS 15 (also observed on the bun preset)
  • @tanstack/react-start: 1.168.26
  • @tanstack/react-router: 1.170.16
  • @tanstack/router-core: 1.171.13 (watchdog identical in 1.171.14)
  • @tanstack/nitro-v2-vite-plugin: 1.155.0
  • vite: 6.3.5, node: 23.11.0

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 with the minimal reproducer and the transformStreamWithRouter watchdog, then trace request-abort handling through the @tanstack/nitro-v2-vite-plugin dev adapter and its adapting Node Readable. Reproduce with the provided bun commands and compare the failing Vite 6 pipeline with the working Vite 8 nitro/vite pipeline. Done means an aborted SSR request cleans up without a later watchdog crash or dev-server exit.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript, vite
Domain
backend, devtools
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.