Start SSR: singleton getRouter() silently leaks request-scoped router state across requests - please add dev warning
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
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
- Create a Start app with SSR enabled
- Make
getRouter()return a singleton:const router = createRouter({ routeTree }) export function getRouter() { return router } - Start the server
- Send a request to a URL that triggers a redirect (e.g., a route with
beforeLoadthat throwsredirect(), or a URL that triggers URL canonicalization like//double-slash-path) - Send a normal GET request to
/- it returns the stale 307 redirect from step 4 - 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
- Dev-time warning in
createStartHandlerwhen the same router instance is observed across requests (low-risk, high-value) - 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 Nitronode-serverpreset
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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