TanStack / TanStack/router

Start SSR: singleton getRouter() silently leaks request-scoped router state across requests - please add dev warning

Open
#6,924 2 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
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

The routing docs correctly state that getRouter() must return a new router instance each time. However, violating this contract fails catastrophically and silently under SSR - no error, no warning, and the app works perfectly for hours before breaking.

When getRouter() returns a singleton, request-scoped router state (including redirect, matches, location, resolvedLocation, statusCode) leaks across server requests. In my case, a single bot request that triggered URL canonicalization set router.state.redirect on the shared instance, and every subsequent HTML GET request returned a 307 redirect until the process was restarted. POST requests (server functions) were unaffected because they take the early server-action branch before router.load().

This is easy to introduce because the natural pattern when you need a stable reference for type registration looks like:

export const router = createRouter({ routeTree })

export function getRouter() {
  return router // singleton - catastrophic under SSR, no warning
}
Steps to reproduce
  1. Create a Start app with SSR enabled
  2. Make getRouter() return a singleton:
    const router = createRouter({ routeTree })
    export function getRouter() { return router }
    
  3. Start the server
  4. Send a request to a URL that triggers a redirect (e.g., a route with beforeLoad that throws redirect(), or a URL that triggers URL canonicalization like //double-slash-path)
  5. Send a normal GET request to / - it returns the stale 307 redirect from step 4
  6. All subsequent GET requests continue returning the stale redirect until the process is restarted
Expected behavior

In development, createStartHandler should detect when getRouter() returns the same router instance across multiple requests and emit a warning:

[TanStack Start] getRouter() returned the same router instance across multiple
server requests. This will cause request-scoped state to leak between requests.
getRouter() must return a new createRouter() instance each call.
See: https://tanstack.com/start/latest/docs/framework/react/guide/routing

A simple referential equality check against the previous call's return value would catch this.

Request
  1. Dev-time warning in createStartHandler when the same router instance is observed across requests (low-risk, high-value)
  2. Docs update - add an explicit "don't do this" example showing the singleton anti-pattern alongside the correct pattern in the routing guide
Workaround

Return a fresh createRouter() per call:

const routerOptions = { routeTree, scrollRestoration: true }

export function getRouter() {
  return createRouter(routerOptions)
}
Additional context
  • The failure mode is severe: complete site outage for all SSR GET requests, with no errors in logs
  • It is time-delayed: the app works after restart until a request triggers any redirect, which can take hours depending on traffic patterns
  • POST requests (server functions) always work, making diagnosis harder
  • I observed this on @tanstack/react-start ~1.166.x with Nitro node-server preset

I am happy to submit a PR for the dev warning and/or docs update if you would like - just let me know the preferred approach.

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 at the createStartHandler entry point and compare its request handling with the routing guide linked in the issue. Verify repeated getRouter() results across SSR requests, then cover the development warning and add the requested singleton anti-pattern alongside the correct pattern in the routing documentation.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
backend, documentation
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.