Skip to content

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

Description

@jayscambler

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions