You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Hydration renders the document root with beforeLoad-provided context stripped when the root fails commitment #8128
hydrate() (router-core load-client.ts) merges a dehydrated match's beforeLoad context (b) only for committed matches. A root that fails commitment — the dehydrated match id doesn't equal the client-rebuilt id (a URL rewrite/serialization disagreement between server and client), or the dehydrated entry is pending/stale — gets context = { ...routerContext, ...routeContext } with every beforeLoad-provided key missing.
That stripped context then gets rendered: the pending document root can't be replaced by pending UI (Match.tsx: "Replacing an SSR document root with pending UI would remove <html>. Hydrated matches retain their prior data, so keep rendering it"), so the root's real component renders and useRouteContext() is missing everything beforeLoad provides. The comment's assumption is exactly what fails — an uncommitted root has no prior data.
This is distinct from #8115: that issue is the contextualize lane window during client-side navigation (vectors addressed by #8084 and #8116). This one drops the context a phase earlier, at the hydration merge — the repro below fails identically on main and on #8116's branch.
Your Example Website or App
Deterministic regression test in the linked PR (packages/react-router/tests/issue-8115-hydration-context-window.test.tsx): dehydrate a root with an id skew plus b: { locale: 'en' }, render while hydration is in flight, and the root's real component renders with useRouteContext().locale === undefined before recovering (['undefined', 'en']).
Expected behavior
A hydrated root never renders with a context missing its beforeLoad-provided keys — either the dehydrated context is available to the render, or the real component is withheld until the client beforeLoad settles.
Actual behavior
The root component renders with the stripped context and, in our app, crashes into the root errorComponent (the locale from root beforeLoad seeds the i18n runtime — same production symptom as #8115). After upgrading to 1.170.30 / router-core 1.171.25 we still measured it at ~2–3 events/hour, concentrated in bursts right after each deploy (stale tabs reloading → a wave of fresh hydrations), overwhelmingly on mobile browsers hydrating our ssr: false shell route — i.e., precisely where hydration windows are widest.
Suggested fix
PR attached: track beforeLoad settlement on the client — contextualize records __beforeLoadContext on settle (mirroring load-server), hydrate() restores it from the dehydrated b for committed matches — and gate the document-root pending exception on it, so a root whose beforeLoad has never contributed presents pending instead of rendering with missing context keys.
Platform
OS: any (production impact concentrated on Android/mobile)
Version: @tanstack/react-router 1.170.30 / @tanstack/router-core 1.171.25 (repro verified on current main)
Activity
Sheraff commentedon Aug 20, 2026
(discussed on slack) This is a skew issue caused by non-isomorphic transformation of the match IDs
fix(router-core): clear __beforeLoadContext when beforeLoad re-runs