Start: <Hydrate> boundaries lose their server HTML when a context above changes before the gate opens
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 45/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- playwright, react, typescript, vite
调研方向
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.
由索引模型根据 Issue 内容生成。
描述
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):
propagateContextChangesreaches the dehydrated fragment (fiber tag 18). It cannot know whether there are consumers inside, so it schedules work on the parent Suspense boundary.updateDehydratedSuspenseComponentsees the update and attempts hydration at a bumped lane (SelectiveHydrationException).- That attempt suspends again on the closed gate. On the retry lane React calls
renderDidSuspendDelayIfPossible()andretrySuspenseComponentWithoutHydrating(...). 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
- Production build (
vite build) with a<Hydrate when={visible()}>boundary below the fold, under a provider whose value changes in a mount effect. - Load the page and do not scroll.
- Check the DOM shortly after hydration. The boundary's server HTML is gone; a
MutationObservershows 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-start1.169.2 (react-start-client1.168.42;visible.tsxis unchanged onmain)react/react-dom19.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.
- 主要语言
- TypeScript
- 星标
- 15.2k
- 派生
- 1.9k
- 平均合并
- 1 天 4 小时
- 30 天内合并 PR
- 63
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
TanStack/router 的其他 Issue
-
router.startViewTransition leaves the ready/finished promises unhandled, so a skipped transition throws an unhandled AbortError可能已有人在做 @unjica 于 5 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 74/100
TanStack/router#8776 · 3 条评论 · 1 个 reaction ·
维护者通常 1 天内回复
-
Router devtools crash when search params contain a BigInt (JSON.stringify in history tracking)可能已有人在做 @A-Raphie 于 9 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
docs: `createLink` has no API reference page; 3 how-to guides link to a missing `#createlink` anchor可能已有人在做 @A-Raphie 于 9 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 88/100
维护者通常 1 天内回复
-
Inline scripts are duplicated after hydration when the CSP nonce is sent in a header可能已有人在做 @gabriel-le 于 29 天前认领。 未关闭
难度 1/5 1 小时以内 新手友好度 90/100
维护者通常 1 天内回复
-
start-plugin-core: sitemap lastmod and pages.json lastBuilt are stamped with the build's wall clock (no SOURCE_DATE_EPOCH support)可能已有人在做 @treadnot 于 31 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 84/100
维护者通常 1 天内回复
相似的 Issue
-
enhancement
难度 2/5 1-3 小时 新手友好度 70/100
b310-digital/teammapper#1515 ·
维护者通常 1 天内回复
-
onenote
难度 2/5 1-3 小时 新手友好度 66/100
obsidianmd/obsidian-importer#689 ·
-
In a half-screen window, the title bar cuts the workspace and version names until they can't be read未关闭bug launch local use macOS Windows
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 62/100
ava-labs/builders-hub#4826 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复