Skip to content

router-core@1.171.7+: SSR query stream never closes with ssr-query → "Serialization timeout after app render finished" #7529

@radosek

Description

@radosek

Bug

router-core@1.171.7+ regresses SSR streaming when used with @tanstack/react-router-ssr-query: the dehydration query stream is never closed, so the response stream hangs until the 60s serialization timeout fires ("Serialization timeout after app render finished"). Real browsers paint early; curl/bots see the full ~30–60s hang.

Root cause

PR #7497 ("fix streaming", landed in router-core@1.171.7) added a reserveStreamFastPath() fast path and guarded onRenderFinished:

// router-core 1.171.6 (works):
onRenderFinished: (listener) => renderFinishedListeners.push(listener),

// router-core 1.171.7+ (regressed):
reserveStreamFastPath() {
  if (!cleanupStarted && _serializationFinished && !streamFastPathReserved
      && renderFinishedListeners.length === 0 && !injectedHtmlBuffer && !scriptBuffer.hasPending()) {
    streamFastPathReserved = true;
    return true;
  }
  return false;
},
onRenderFinished: (listener) => {
  if (cleanupStarted || streamFastPathReserved) return;   // ← silently drops the listener
  renderFinishedListeners.push(listener);
},

@tanstack/router-ssr-query-core@1.169.1 registers the query-stream close via onRenderFinished:

// router-ssr-query-core 1.169.1, setupCoreRouterSsrQueryIntegration
router.serverSsr.onRenderFinished(() => {
  if (!queryStream.isClosed()) queryStream.close();
  ...
});

When reserveStreamFastPath() has already set streamFastPathReserved = true by the time the integration registers its onRenderFinished listener, that listener is silently dropped. queryStream is therefore never closed → serializationFinished never becomes true → transformStreamWithRouter hits the serialization timeout.

Versions

  • Broken: @tanstack/react-router@1.170.x (→ router-core@1.171.71.171.9) + @tanstack/react-router-ssr-query@1.167.1 (→ router-ssr-query-core@1.169.1)
  • Works: @tanstack/react-router@1.169.0 (→ router-core@1.169.0), or router-core@1.171.6 (pre-fix: fix streaming #7497, no guard)

Confirmed onRenderFinished is unguarded in 1.171.6 and guarded in 1.171.7, 1.171.8, 1.171.9.

Reproduction

React SSR with streaming (renderToReadableStream + transformReadableStreamWithRouter) and setupRouterSsrQueryIntegration, where a route loader uses ensureQueryData/prefetchQuery so there are dehydrated queries. On a route that resolves all queries quickly, reserveStreamFastPath() fires before the ssr-query integration registers its close listener → the stream hangs.

Expected

The latest published @tanstack/react-router-ssr-query is 1.167.1, which does not appear to account for reserveStreamFastPath, so there is currently no version combination on router-core@1.171.7+ that closes the query stream. Either:

  1. onRenderFinished should still register (or immediately invoke) the listener when the fast path is already reserved, or
  2. ship a router-ssr-query-core release coordinated with the reserveStreamFastPath change.

Happy to provide a minimal repro repo if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    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