From b9e7e852407e87b22ff192074a57363f76c0f1d1 Mon Sep 17 00:00:00 2001 From: zan Date: Mon, 24 Aug 2026 12:32:56 +0000 Subject: [PATCH] fix(router-ssr-query-core): skip hydrate on stream close Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/olive-pugs-shave.md | 5 +++ packages/router-ssr-query-core/src/index.ts | 2 +- .../tests/stream-close.test.ts | 38 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .changeset/olive-pugs-shave.md create mode 100644 packages/router-ssr-query-core/tests/stream-close.test.ts diff --git a/.changeset/olive-pugs-shave.md b/.changeset/olive-pugs-shave.md new file mode 100644 index 00000000000..3ead4f59c3d --- /dev/null +++ b/.changeset/olive-pugs-shave.md @@ -0,0 +1,5 @@ +--- +'@tanstack/router-ssr-query-core': patch +--- + +skip hydration when the query stream closes diff --git a/packages/router-ssr-query-core/src/index.ts b/packages/router-ssr-query-core/src/index.ts index 685653dea0c..0425728c3f7 100644 --- a/packages/router-ssr-query-core/src/index.ts +++ b/packages/router-ssr-query-core/src/index.ts @@ -195,10 +195,10 @@ export function setupCoreRouterSsrQueryIntegration({ reader .read() .then(async function handle({ done, value }) { - queryHydrate(queryClient, value, hydrateOptions) if (done) { return } + queryHydrate(queryClient, value, hydrateOptions) const result = await reader.read() return handle(result) }) diff --git a/packages/router-ssr-query-core/tests/stream-close.test.ts b/packages/router-ssr-query-core/tests/stream-close.test.ts new file mode 100644 index 00000000000..3f757fe84d6 --- /dev/null +++ b/packages/router-ssr-query-core/tests/stream-close.test.ts @@ -0,0 +1,38 @@ +import { QueryClient, hydrate } from '@tanstack/query-core' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { setupCoreRouterSsrQueryIntegration } from '../src' + +vi.mock('@tanstack/query-core', async (importOriginal) => { + const actual = await importOriginal() + return { ...actual, hydrate: vi.fn(actual.hydrate) } +}) + +describe('setupCoreRouterSsrQueryIntegration', () => { + beforeEach(() => { + vi.mocked(hydrate).mockClear() + }) + + it('does not hydrate once the query stream is closed', async () => { + const queryClient = new QueryClient() + const router = { isServer: false, options: {} } as { + isServer: boolean + options: { hydrate?: (dehydrated: any) => unknown | Promise } + } + + setupCoreRouterSsrQueryIntegration({ router: router as any, queryClient }) + + const stream = new ReadableStream({ + start(controller) { + controller.enqueue({ mutations: [], queries: [] }) + controller.close() + }, + }) + + await router.options.hydrate?.({ queryStream: stream }) + await vi.waitFor(() => expect(hydrate).toHaveBeenCalled()) + + expect( + vi.mocked(hydrate).mock.calls.some(([, state]) => state === undefined), + ).toBe(false) + }) +})