Bug: hydration completes in Header before page renders
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
Bug: useHydrated inconsistent across Root shellComponent and page routes (hydration completes in Header before page)
Description
I’m running into a serious hydration inconsistency when using TanStack Start (SSR) with a shellComponent (RootDocument) that renders a <Header />.
The issue is that hydration appears to complete inside the Header before the page route hydrates, causing useHydrated() to return:
truein the Headerfalsein the page component after hydration already occurred
Once hydration becomes true, it should never flip back to false, but that’s exactly what’s happening.
This causes real-world bugs where:
- Data fetching hooks complete in the Header
- Pages render with SSR state (
isPending: true) - Pages never receive the client-side update
- Inputs remain permanently disabled, skeletons never disappear, etc.
Code Samples
Root Route (shellComponent)
export const Route = createRootRoute({
shellComponent: RootDocument,
})
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body>
<Providers>
<Header />
{children}
</Providers>
<Scripts />
</body>
</html>
)
}
Header (hydration becomes true early)
export function Header() {
const { isPending } = authClient.useSession()
const isHydrated = useHydrated()
console.log("Header:", { isHydrated, isPending })
return (...)
}
Page Route (useHydrated becomes false again)
function SettingsPage() {
const isHydrated = useHydrated()
console.log("Page:", { isHydrated })
return <Settings />
}
⸻
Actual Behavior
• Header logs isHydrated: true
• Page logs isHydrated: false
• Page never receives client-side updates from hooks
• SSR state (e.g. isPending: true) is permanently stuck
Example real-world workaround (required to avoid broken UI):
disabled={isPending || !isHydrated}
This should not be necessary.
⸻
Expected Behavior
• Hydration should be global and monotonic
• Once isHydrated === true, it should never revert
• Header and page should observe the same hydration lifecycle
• Data fetched in shell components should not “complete early” relative to page hydration
⸻
Why This Is Severe
This breaks:
• Pending states
• Disabled inputs
• Auth/session-based layouts
• Any app that fetches data in a shared shell component
It makes SSR apps behave inconsistently and causes bugs that are extremely difficult to diagnose.
⸻
Suspected Cause
It feels like:
• The shellComponent is being hydrated in a separate pass / chunk
• Page routes are still treated as “not hydrated” afterward
• Hydration context is not shared correctly between shell and route components
⸻
Notes
Happy to reduce this further if needed, but this repro already demonstrates the issue clearly.
Thanks for taking a look 🙏
Your Example Website or App
https://github.com/better-auth-ui/better-auth-ui/tree/v4/examples/start-heroui-example
Steps to Reproduce the Bug or Issue
Reproduction
Minimal reproduction repo:
🔗 https://github.com/better-auth-ui/better-auth-ui/tree/v4/examples/start-heroui-example
Steps:
- bun install
- nx dev start-heroui-example
- Sign up a random account (no db required)
- Go to settings
- Observe hydration error
Expected behavior
Hydration should not complete on the RootComponent before a Child SSR route.
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 linked start-heroui-example reproduction: run bun install and nx dev start-heroui-example, then inspect the shellComponent/RootDocument, Header, page route, and useHydrated() usage described in the issue. Trace how hydration state is shared between the shell and child SSR route. Done means Header and page observe one monotonic hydration lifecycle and the settings page receives client-side updates without the workaround.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend, full-stack
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100