fix(router-core): fall back to CSR when SSR bootstrap data is missing during hydration#7674
fix(router-core): fall back to CSR when SSR bootstrap data is missing during hydration#7674anonrig wants to merge 1 commit into
Conversation
… during hydration `hydrate()` threw `Invariant failed` and crashed the whole app through the error boundary when `window.$_TSR` (or `window.$_TSR.router`) was absent. That data can legitimately be missing when the streamed HTML is truncated or the inline bootstrap script never runs: aborted navigations, crawlers/link-preview bots, in-app webviews, and CSP/extension-blocked inline scripts. Instead of throwing, hydration now bails out and lets the client render from scratch. Leaving `router.ssr` unset makes the Transitioner run `router.load()` on mount (the same path a pure client-side app uses), so the page recovers instead of white-screening. A dev-only warning is logged to surface genuine SSR misconfiguration.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthrough
hydrate() SSR bootstrap fallback
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
"t can legitimately be absent at hydration time when:" really? we ensure that is it really athing that some clients dont execute inline scripts, but execute external scripts? |
|
We see this error as "invariant failed" and nothing else. So hard to explain and reproduce. |
Summary
hydrate()throwsInvariant failedand crashes the entire app (the error propagates throughAwait/useAwaitedto the nearest error boundary) when the SSR bootstrap global is missing:window.$_TSRis populated by an inline<script>that the server streams with the HTML. It can legitimately be absent at hydration time when:In all of these cases the right behavior is to render on the client, not to white-screen. Today the hard
invariant()turns a recoverable condition into a full app crash.This is the same class of problem reported in #7524 ("the
$_TSRis deleted too early, and ends up undefined"). That PR tried to delay teardown and was reverted in #7533 in favor of immediate teardown — so the absence of$_TSRstill surfaces as a fatal invariant rather than being handled.Fix
When the bootstrap data is missing, bail out of hydration instead of throwing. Leaving
router.ssrunset means theTransitionerrunsrouter.load()on mount — exactly the path a pure client-side app uses:So the page recovers with a normal client render instead of crashing. A dev-only
console.warnis logged so genuine SSR misconfiguration is still easy to spot (the message is stripped from prod bundles by the existingprocess.env.NODE_ENVguard).This lives in core
hydrate(), so the React / Solid / Vue start clients all benefit.Behavior change
window.$_TSR/window.$_TSR.router→Invariant failedthrown → app crashes to the error boundary.router.load(); dev-only warning logged.Tests
hydrate.test.ts: the two cases that asserted a throw now assert graceful fallback (hydrateresolves,matchRoutesis not called,router.ssrstays unset, a dev warning is emitted).@tanstack/router-coreunit suite passes (1178 passed, 3 expected-fail), no type errors, eslint clean.Summary by CodeRabbit