TanStack / TanStack/router

Build hangs after prerender: SSR timeouts in transformStreamWithRouter are never cleaned up

Open
#6,602 7 comments 6 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Description

This issue was filed with AI assistance. My builds were hanging indefinitely when prerender was enabled. I read the docs multiple times and reviewed related open issues (e.g. #6425), but couldn't resolve it. After ruling out misconfiguration, I spent about an hour debugging with Claude Opus and we identified the root cause below.

When using TanStack Start with prerendering enabled, the vite build process hangs indefinitely after all pages are prerendered. The process never exits, blocking CI pipelines and requiring manual termination.

Root Cause

@tanstack/router-core/dist/esm/ssr/transformStreamWithRouter.js creates 60-second setTimeout handles (lifetime timeout and serialization timeout) for each SSR-rendered page. During prerendering, these timeouts are never cleaned up because:

  1. Each call to transformStreamWithRouter() sets a lifetimeTimeoutHandle (60s) at line ~231
  2. After the app stream finishes rendering, serializationFinished is false, so a serializationTimeoutHandle (60s) is also set at line ~315
  3. The onSerializationFinished event never fires during prerendering, so tryFinish()cleanup() is never called
  4. This leaves one timeout per prerendered page as active Node.js handles, preventing the process from exiting

With 49 prerendered pages, there are ~50 active Timeout handles keeping the event loop alive.

Reproduction

Any TanStack Start project with prerendering enabled:

// vite.config.ts
tanstackStart({
  pages: [
    { path: '/page1', prerender: { enabled: true } },
    { path: '/page2', prerender: { enabled: true } },
    // ...
  ],
})

Run vite build — the process will hang after printing "Prerendered N pages".

Environment

  • @tanstack/react-start: 1.158.3
  • @tanstack/router-core: 1.158.3
  • Vite: 7.3.1
  • Node.js: v24.7.0

Suggested Fix

Add .unref() to all setTimeout calls in transformStreamWithRouter.ts so they don't prevent the Node.js process from exiting. These are safety timeouts — they should fire if the process is still running, but shouldn't keep the process running:

// Line ~96 (early return path lifetime timeout)
  lifetimeTimeoutHandle2 = setTimeout(() => {
    // ...
  }, lifetimeMs2);
+ lifetimeTimeoutHandle2.unref();

// Line ~231 (main path lifetime timeout)
  lifetimeTimeoutHandle = setTimeout(() => {
    // ...
  }, lifetimeMs);
+ lifetimeTimeoutHandle.unref();

// Line ~315 (serialization timeout)
  serializationTimeoutHandle = setTimeout(() => {
    // ...
  }, timeoutMs);
+ serializationTimeoutHandle.unref();

Alternatively, the prerender flow should ensure onSerializationFinished fires (or cleanup() is called) after each page response is fully consumed, so the timeouts are properly cleared.

Workaround

As a temporary workaround, add process.exit(0) at the end of postServerBuild in @tanstack/start-plugin-core/dist/esm/post-server-build.js:

if (startConfig.prerender?.enabled) {
  process.exit(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 packages/router-core/src/ssr/transformStreamWithRouter.ts and inspect the lifetime and serialization timeout paths around the referenced lines. Reproduce the issue with a TanStack Start vite.config.ts containing prerendered pages, then run vite build and verify that the process exits after all pages are prerendered without lingering timeout handles.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript, vite
Domain
backend, build-system
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
67/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.