From 9f74e50842bd3b01a3afecf0f33969a60c715c68 Mon Sep 17 00:00:00 2001 From: Christoph Nakazawa Date: Mon, 6 Jul 2026 17:30:22 +0900 Subject: [PATCH] fix: Preserve client navigation for deferred props. --- packages/void/src/pages/protocol.ts | 2 ++ .../void/test/unit/pages-protocol.test.ts | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/void/src/pages/protocol.ts b/packages/void/src/pages/protocol.ts index 7788ac7ba..d6d7eaed2 100644 --- a/packages/void/src/pages/protocol.ts +++ b/packages/void/src/pages/protocol.ts @@ -473,6 +473,8 @@ export async function handlePageGet( return pageJsonResponse(c, pageObj); } // Stream NDJSON: first line is PageObject, subsequent lines are deferred groups + c.header('X-VoidPages', 'true'); + c.header('Vary', 'X-VoidPages'); return streamDeferredResponse( JSON.stringify(pageObj) + '\n', deferredMap, diff --git a/packages/void/test/unit/pages-protocol.test.ts b/packages/void/test/unit/pages-protocol.test.ts index cc9d73462..87f198f03 100644 --- a/packages/void/test/unit/pages-protocol.test.ts +++ b/packages/void/test/unit/pages-protocol.test.ts @@ -794,6 +794,26 @@ describe('handlePageMutation', () => { }); describe('handlePageGet with deferred props', () => { + it('SPA — marks deferred NDJSON responses as Void Pages responses', async () => { + const c = await createTestContext('GET', '/dashboard', { + headers: { 'x-voidpages': 'true' }, + }); + + const response = await handlePageGet( + () => ({ + usage: defer(() => Promise.resolve({ requests: 100 })), + }), + undefined, + c, + 'dashboard', + mockRenderFn, + mockGetAssetTags, + ); + + expect(response.headers.get('X-VoidPages')).toBe('true'); + expect(response.headers.get('Vary')).toBe('X-VoidPages'); + }); + it('SPA — streams NDJSON with immediate props first, then deferred', async () => { const c = await createTestContext('GET', '/dashboard', { headers: { 'x-voidpages': 'true' },