-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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.