SSR-enabled beforeLoad on root with ssr: false causes "This Suspense boundary received an update before it finished hydrating."
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?
Router
Describe the bug
When using SSR with tanstack start and router, with a route that has a beforeLoad, a router change-of-state triggers a useSyncExternalStore update / callback. Since useSyncExternalStore is always synchronous, it ignores the startTransition scope of the call stack, fires off a state change immediately, and invalidates the Suspense boundary of the SSR hydration.
The result is this error:
react-dom.development.js:20741 Uncaught Error: This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client rendering. The usual way to fix this is to wrap the original update in startTransition.
at updateDehydratedSuspenseComponent (react-dom.development.js:20741:48)
at updateSuspenseComponent (react-dom.development.js:20401:16)
at beginWork (react-dom.development.js:21663:14)
at beginWork$1 (react-dom.development.js:27465:14)
at performUnitOfWork (react-dom.development.js:26596:12)
at workLoopSync (react-dom.development.js:26505:5)
at renderRootSync (react-dom.development.js:26473:7)
at performSyncWorkOnRoot (react-dom.development.js:26124:20)
at flushSyncCallbacks (react-dom.development.js:12042:22)
at flushPassiveEffectsImpl (react-dom.development.js:27099:3)
(Note the fix supplied in the error text is wrong. useSyncExternalStore is never compatible with suspense. See here).
Complete minimal reproducer
https://github.com/fionamatthews-wk/tanstack-router-beforeload-ssr-hydration-error
Steps to Reproduce the Bug
- pnpm install
- pnpm dev
- Visit http://localhost:3000
Note hydration error in console.
Expected behavior
No hydration error. SSR correctly loads and hydrates the server copy without falling back to a full client render.
Screenshots or Videos
No response
Platform
- Router / Start Version: 1.170.32 / 1.168.49
- OS: MacOS
- Browser: Chrome
- Browser Version: 151.0.7922.173
- Bundler: vite
- Bundler Version: 8.2.2
Additional context
It seems likely that #7805 introduced this.
#8055 seems like an attempt to fix this, but our specific case is still receiving a suspense boundary and erroring as of 1.170.32.
I suspect the issue is more to do with calling beforeLoad triggers a store change that a component re-renders for, rather than a question of whether or not the root should be wrapped in suspense.
I placed a breakpoint inside of most useSyncExternalStore subscribers during the initial hydration, and Match.tsx was the first component I found whose useSyncExternalStore (via useStore) is triggering a react update event. Previously, Match.tsx had a filter on its store change. Now, it does not. I suspect that this change, or others like it, are what is causing a synchronous state change callback during suspense that causes react to bail.
I do wonder if ssr: false on a root with SSR enabled is in conflict. However, our current intent is to use SSR for injection of nonces, with investigation into enabling SSR on specific routes postponed until later. Changing ssr to true in the reproduction does get rid of the error, but our intent is not to SSR the root at this time and still have beforeLoad implement our authentication.
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 with the supplied reproducer: run pnpm install, pnpm dev, and visit http://localhost:3000 to observe the hydration error. Then inspect packages/react-router/src/Match.tsx, especially its useStore/useSyncExternalStore behavior during beforeLoad and initial hydration, along with the linked earlier version and referenced issues. Done means the SSR-enabled root with ssr: false hydrates without the Suspense error or a full client-render fallback.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100