HydrationBoundary ignores/skips server-prefetched query when a parent useQuery with same key is rendered before child useSuspenseQuery
Maintainer thường phản hồi trong vòng 1 ngày
@mgarcialeniolabs đang làm issue này rồi.
Từ ngày 27/8/2026.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 52/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- react, typescript
- Lĩnh vực
- frontend
Hướng nghiên cứu
Bắt đầu với bản tái hiện CodeSandbox được liên kết và theo dõi hành vi của HydrationBoundary xung quanh useQuery của parent và useSuspenseQuery của child, sử dụng flow của app/(dashboard)/todos/[todoId]/layout.tsx hoặc page.tsx được mô tả trong báo cáo. So sánh các trạng thái prefetch phía server, dehydration và hydration; hoàn tất khi key đã được prefetch vẫn khả dụng cho child mà không có yêu cầu SSR trùng lặp hoặc lỗi client-request guard.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the bug
If a useQuery is rendered before a HydrationBouhndary, hydrations for that key will be skipped despite the fact that the useQuery would have not fetched in the first place.
In our case we have a prefetch where we pass the dehydrated queryClient to a HydrationBoundary. But there is a useQuery using the same key as the prefetched query above the hydration boundary that (in some global component higher up) gets rendered. The hydration boundary skips this hydration and the child components of the HydrationBoundary that have useSuspenseQuery for that key still run their queries in SSR.
Small example:
function Page({ params }) {
const queryClient = getQueryClient();
void queryClient.prefetchQuery(
buildTodoQueryOptions(params.todoId, makeServerRequest),
);
return (
<>
<SomeHeaderComponent /> {/* a `useQuery` lives in here */}
<HydrationBoundary state={dehydrate(queryClient)}>
<MyChildComponent /> {/* a `useSuspenseQuery` lives in here and fires despite the above prefetch */}
</HydrationBoundary>
</>
);
}
Also, in our particular case this triggers a guard we have to ensure we do not client fetch on the server during SSR. This also means the request is being duplicated and firing twice (in our prefetch and during SSR, which should have been skipped)
Flow (all on the server):
- Parent/route boundary prefetches the detail query
todoIdon the server. - A higher component in layout/header renders a
useQuerywith the keytodoId. - Pass the dehydrated and prefetched
queryClientto aHydrationBoundarylower than theuseQuery. - The HydrationBoundary` skips this key and does not hydrate the prefetched query as expected.
- Child
useSuspenseQuerythen attempts a fetch with client requestor during SSR and hits the guard error.
Repro
Server prefetch at detail layout/page:
// In app/(dashboard)/todos/[todoId]/layout.tsx or page.tsx
function Page({ params }) {
const queryClient = getQueryClient();
void queryClient.prefetchQuery(buildTodoQueryOptions(todoId, makeServerRequest));
return <HydrationBoundary state={dehydrate(queryClient)}>{children}</HydrationBoundary>;
}
Global/header client component that also reads URL todoId (lives OUTSIDE/above the above layout.tsx)
"use client";
import { useQuery, useParams } from "@tanstack/react-query";
function TodoHeaderMenu() {
const params = useParams();
const todoId = typeof params.todoId === "string" ? params.todoId : "";
const { data: todo } = useQuery({
...buildTodoQueryOptions(todoId),
enabled: Boolean(todoId),
});
return <span>{todo?.title}</span>;
}
Child detail component:
"use client";
function TodoPage({ todoId }: { todoId: string }) {
const { data: todo } = useSuspenseQuery(buildTodoQueryOptions(todoId));
return <TodoDetails todo={todo} />;
}
buildTodoQueryOptions defaults to client requestor. Client-request guard throws:
if (typeof window === "undefined") {
throw new Error(
"Warning, aborting SSR. You attempted to fetch from the server with clientRequest."
);
}
AI disclosure: I had codex help me with the quick repro.
Questions
- Is this current behavior expected?
- Should hydration logic be changed to only skip queries that are fetching (aka don't skip
useQueriesthat fill the cache key on the server but do not initiate filling the value on the server)
Your minimal, reproducible example
https://codesandbox.io/p/devbox/pr7c4g
Steps to reproduce
The home page on that will bring up the error in the next dev error popup
Expected behavior
I would expect a useQuery in a higher component with the same key to not STOP the server fetch/hydration given useQuery would not trigger the client request guard like useSuspenseQuery does. useQuery does not start the queryFn on the server, so it should not stop useSuspenseQueries that CAN imo.
This also comes with the additional problem that because of the useQuery, the useSuspenseQuery is firing again, ruining deduplication.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
macOS
Chromium
next: 16.1.1
Tanstack Query adapter
react-query
TanStack Query version
5.90.21
TypeScript version
No response
Additional context
I can technically gate the useQuery in the parent component with something like
const isDealQueryReady = !!(
state &&
state.data !== undefined &&
state.status !== "pending" &&
state.fetchStatus !== "fetching"
)
//and then
if (!isDealQueryReady) {
return null;
}
//...
//...`useQuery`
and then I do not get the error but that is obnoxious.
- Ngôn ngữ chính
- TypeScript
- Star
- 50.4k
- Fork
- 4.2k
- Merge trung bình
- 11 giờ 33 phút
- Pull request đã merge (30 ngày)
- 421
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của TanStack/query
-
solid-query: STRICT_READ_UNTRACKED on Solid 2 — client()/options() read in component body (useMutation, useBaseQuery)Có thể làm lại được Pull request cho issue này đã bị đóng mà không được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
TanStack/query#11358 · 2 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
solid-query: switching the queryClient accessor strands the new client's cache (subscription stays on the old observer)Có thể đã có người làm @MaNaN1803 đã nhận 75 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
TanStack/query#11106 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
Maintainer thường phản hồi trong vòng 1 ngày
-
setQueryData: NoInfer loses discriminated-union members when spreading a narrowed updater valueCó thể đã có người làm @iosayin đã nhận 1 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 1 ngày
-
setQueryData updater loses discriminated-union fields when spreading inferred NoInfer dataCó thể đã có người làm @boriskozak đã nhận 1 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 68/100
TanStack/query#11794 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của TanStack/query
Issue tương tự
-
area:docs bug triage:confirmed
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
Cotal-AI/Cotal#2875 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
GLM-5.3 available on bedrock nowĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
anomalyco/models.dev#8862 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
fix(data-lake): wizard source step still previews the local slug, not the server-disambiguated oneCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mởdata-lake
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
ready-for-triage
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
konflux-ci/konflux-ui#1596 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement good first issue priority: low size: XS
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày