From 6a9437da6b85f0182d724b6b34170b41297db8fd Mon Sep 17 00:00:00 2001 From: Nic Polumeyv <162764842+Nic-Polumeyv@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:20:09 -0400 Subject: [PATCH] fix: discard a consumed preloaded redirect before following it Co-authored-by: Benjamin Davis --- .changeset/pre/quiet-preload-redirect.md | 5 +++++ packages/kit/src/runtime/client/client.js | 3 +++ .../preloading/redirect/[step]/+page.server.js | 13 +++++++++++++ .../preloading/redirect/[step]/+page.svelte | 1 + .../basics/test/cross-platform/client.test.js | 18 ++++++++++++++++++ 5 files changed, 40 insertions(+) create mode 100644 .changeset/pre/quiet-preload-redirect.md create mode 100644 packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.server.js create mode 100644 packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.svelte diff --git a/.changeset/pre/quiet-preload-redirect.md b/.changeset/pre/quiet-preload-redirect.md new file mode 100644 index 000000000000..4fc6a4af2540 --- /dev/null +++ b/.changeset/pre/quiet-preload-redirect.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: don't replay a preloaded redirect when a later hop of the navigation returns to the route diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 4813816160c5..4a4926079a4b 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -2072,6 +2072,9 @@ async function navigate({ if (navigation_result.type === 'redirect') { // whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects if (redirect_count < 20) { + // a preloaded redirect has been consumed; a later hop back to this route must load afresh + if (load_cache?.id === intent?.id) discard_load_cache(); + await navigate({ type, url: new URL(navigation_result.location, url), diff --git a/packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.server.js b/packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.server.js new file mode 100644 index 000000000000..e3a0731be731 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.server.js @@ -0,0 +1,13 @@ +import { redirect } from '@sveltejs/kit'; + +/** @type {import('./$types').PageServerLoad} */ +export function load({ cookies, params }) { + if (params.step === 'resolve') { + cookies.set('preload-redirect-resolved', 'true', { path: '/routing/preloading/redirect' }); + redirect(303, '/routing/preloading/redirect/target'); + } + + if (!cookies.get('preload-redirect-resolved')) { + redirect(303, '/routing/preloading/redirect/resolve'); + } +} diff --git a/packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.svelte new file mode 100644 index 000000000000..69633c218884 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/routing/preloading/redirect/[step]/+page.svelte @@ -0,0 +1 @@ +

Redirect resolved

diff --git a/packages/kit/test/apps/basics/test/cross-platform/client.test.js b/packages/kit/test/apps/basics/test/cross-platform/client.test.js index 6c4601fda6dc..c00406b4248b 100644 --- a/packages/kit/test/apps/basics/test/cross-platform/client.test.js +++ b/packages/kit/test/apps/basics/test/cross-platform/client.test.js @@ -1023,6 +1023,24 @@ test.describe('Prefetching', () => { ); }); + test('does not replay a preloaded redirect when a later hop returns to the route', async ({ + page, + app + }) => { + await page.goto('/routing/a'); + + // the cookie is unset, so the preload resolves to a redirect to /resolve + const target = '/routing/preloading/redirect/target'; + expect(await app.preloadData(target)).toMatchObject({ + type: 'redirect', + location: '/routing/preloading/redirect/resolve' + }); + + // /resolve sets the cookie and redirects back to /target, which must now load afresh + await app.goto(target); + await expect(page.locator('h1')).toHaveText('Redirect resolved'); + }); + test('chooses correct route when hash route is preloaded but regular route is clicked', async ({ app, page