Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Start: <Hydrate> boundaries lose their server HTML when a context above changes before the gate opens

Aperta
#8,778 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
45/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
playwright, react, typescript, vite
Ambito
backend, frontend

Direzione di ricerca

Start with packages/react-start-client/src/hydration/visible.tsx and compare the other hydration strategies that use the same gate. Read the deferred-hydration guide and trace the described React 19.3 context update and retry path; reproduce with a production build using the provider and below-the-fold boundary from the report. Done means the server HTML survives an upstream context update before the strategy opens, or the guide clearly documents the limitation.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Which project does this relate to?

Start (@tanstack/react-start, deferred hydration / <Hydrate>)

Describe the bug

A <Hydrate> boundary (e.g. when={visible()}) loses its server-rendered HTML during initial hydration whenever any context provider above it changes its value before the gate opens. The boundary ends up empty (its fallback, null by default) until the strategy fires (e.g. until the user scrolls to it).

This happens in practice in almost any real app, because root providers commonly update right after mount (session probe setting isLogged, attribution/analytics state, a provider that publishes value={{ ... }} and re-renders on router changes, etc.).

The deferred-hydration guide says:

React may hydrate a deferred boundary earlier than its strategy would normally allow if state, props, context, or store updates outside the boundary require React to reconcile inside it before the gate opens. This preserves correctness and avoids showing stale server HTML after the surrounding app has changed.

React does try to do that, but the early hydration can never succeed, because HydrationBoundary keeps throwing the gate promise until the strategy resolves:

// packages/react-start-client/src/hydration/visible.tsx (same shape in the other strategies)
function HydrationBoundary(props) {
  const { g, o } = props
  if (!g.r) {
    if (!reactUse) throw g.p
    reactUse(g.p)
  }
  ...
}

What React 19.3 does (react-dom-client):

  1. propagateContextChanges reaches the dehydrated fragment (fiber tag 18). It cannot know whether there are consumers inside, so it schedules work on the parent Suspense boundary.
  2. updateDehydratedSuspenseComponent sees the update and attempts hydration at a bumped lane (SelectiveHydrationException).
  3. That attempt suspends again on the closed gate. On the retry lane React calls renderDidSuspendDelayIfPossible() and retrySuspenseComponentWithoutHydrating(...). For non-transition updates this commits a client render: the server HTML is deleted and the fallback is shown.

So the correctness promise in the guide doesn't hold: instead of hydrating early, the boundary switches to client rendering and shows nothing until the gate opens.

Your Example Website or App

Minimal shape (no router needed to reproduce the React side):

const Ctx = createContext(0)
function Provider({ children }) {
  const [n, setN] = useState(0)
  useEffect(() => setN(1), []) // any value change after mount
  return <Ctx.Provider value={n}>{children}</Ctx.Provider>
}

<Provider>
  <Header />
  {/* tall content so the boundary is below the fold */}
  <Hydrate when={visible()}>
    <Footer /> {/* does not even read Ctx */}
  </Hydrate>
</Provider>
Steps to Reproduce the Bug or Issue
  1. Production build (vite build) with a <Hydrate when={visible()}> boundary below the fold, under a provider whose value changes in a mount effect.
  2. Load the page and do not scroll.
  3. Check the DOM shortly after hydration. The boundary's server HTML is gone; a MutationObserver shows it removed in the same commit as the provider update. Scroll down: the content renders again on the client.

Hoisting the <Hydrate> element to a module constant, so it never gets new props, doesn't help: the trigger is context propagation, not props.

Expected behavior

Either:

  • the gate opens when React asks to hydrate the boundary early, which is what the guide describes; or
  • the docs state that <Hydrate> only keeps its SSR HTML if no context above it changes before the gate opens.
Platform
  • @tanstack/react-start 1.169.2 (react-start-client 1.168.42; visible.tsx is unchanged on main)
  • react / react-dom 19.3.1
  • Chromium (Playwright), production build served by Nitro
Additional context

We are working around it by moving the state of our root providers into external stores read with useSyncExternalStore, so their context value never changes after mount. The guide does not hint at this, and any third-party provider that updates on mount would still break it.

Lingua principale
TypeScript
Stelle
15.2k
Fork
1.9k
Merge medio
1g 13h
PR unite (30g)
76

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di TanStack/router

Tutte le issue di TanStack/router

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.