Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quick-rivers-shine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: send `cache-control: private, no-store` on remote function responses so personalized query results can never be cached by shared caches
12 changes: 9 additions & 3 deletions packages/kit/src/runtime/server/remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
'sveltekit.remote.call.name': internals.name
});

/** @type {HeadersInit | undefined} */
const headers = state.prerendering ? undefined : { 'cache-control': 'private, no-store' };

try {
/** @type {RemoteFunctionData} */
const data = {};
Expand Down Expand Up @@ -226,7 +229,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
/** @type {RemoteFunctionResponse} */ ({
type: 'result',
data: stringify(data, transport)
})
}),
{ headers }
);
}

Expand Down Expand Up @@ -275,7 +279,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
/** @type {RemoteFunctionResponse} */ ({
type: 'result',
data: stringify(data, transport)
})
}),
{ headers }
);
} catch (error) {
if (error instanceof Redirect) {
Expand All @@ -285,7 +290,8 @@ async function handle_remote_call_internal(event, state, options, manifest, id)
/** @type {RemoteFunctionResponse} */ ({
type: 'result',
data: stringify(data, transport)
})
}),
{ headers }
);
}

Expand Down
9 changes: 9 additions & 0 deletions packages/kit/test/apps/async/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ test.describe('remote functions', () => {
await page.getByRole('button', { name: 'Refresh' }).click();
await expect(page.locator('p')).toHaveText('foobaz');
});

test('remote query responses are not cacheable', async ({ page }) => {
// the query is kicked off during SSR but fetched by the client after
// hydration, so we can observe the response headers on the wire
const response_promise = page.waitForResponse((r) => r.url().includes('/_app/remote/'));
await page.goto('/remote/query-loading-state');
const response = await response_promise;
expect(response.headers()['cache-control']).toBe('private, no-store');
});
});

// have to run in serial because commands mutate in-memory data on the server (should fix this at some point)
Expand Down
Loading