diff --git a/.changeset/modern-years-tell.md b/.changeset/modern-years-tell.md new file mode 100644 index 000000000000..f2d59635cf55 --- /dev/null +++ b/.changeset/modern-years-tell.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: handle redirect thrown in handle hook in response to form action diff --git a/packages/kit/src/runtime/server/page/actions.js b/packages/kit/src/runtime/server/page/actions.js index 33dd11170267..75d9bf039724 100644 --- a/packages/kit/src/runtime/server/page/actions.js +++ b/packages/kit/src/runtime/server/page/actions.js @@ -72,11 +72,7 @@ export async function handle_action_json_request(event, options, server) { const err = normalize_error(e); if (err instanceof Redirect) { - return action_json({ - type: 'redirect', - status: err.status, - location: err.location - }); + return action_json_redirect(err); } return action_json( @@ -100,6 +96,17 @@ function check_incorrect_fail_use(error) { : error; } +/** + * @param {import('types').Redirect} redirect + */ +export function action_json_redirect(redirect) { + return action_json({ + type: 'redirect', + status: redirect.status, + location: redirect.location + }); +} + /** * @param {import('types').ActionResult} data * @param {ResponseInit} [init] diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index f48bffefe849..6d94cde57fc0 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -26,6 +26,7 @@ import { } from '../../utils/exports.js'; import { get_option } from '../../utils/options.js'; import { error, json, text } from '../../exports/index.js'; +import { action_json_redirect, is_action_json_request } from './page/actions.js'; /* global __SVELTEKIT_ADAPTER_NAME__ */ @@ -309,6 +310,8 @@ export async function respond(request, options, manifest, state) { if (e instanceof Redirect) { const response = is_data_request ? redirect_json_response(e) + : route?.page && is_action_json_request(event) + ? action_json_redirect(e) : redirect_response(e.status, e.location); add_cookies_to_headers(response.headers, Object.values(cookies_to_add)); return response; diff --git a/packages/kit/test/apps/basics/src/hooks.server.js b/packages/kit/test/apps/basics/src/hooks.server.js index 7a6bc7affec5..3dc9017fd287 100644 --- a/packages/kit/test/apps/basics/src/hooks.server.js +++ b/packages/kit/test/apps/basics/src/hooks.server.js @@ -112,6 +112,13 @@ export const handle = sequence( return event.fetch('/prerendering/prerendered-endpoint/api'); } + return resolve(event); + }, + async ({ event, resolve }) => { + if (event.url.pathname === '/actions/redirect-in-handle' && event.request.method === 'POST') { + throw redirect(303, '/actions/enhance'); + } + return resolve(event); } ); diff --git a/packages/kit/test/apps/basics/src/routes/actions/redirect-in-handle/+page.server.js b/packages/kit/test/apps/basics/src/routes/actions/redirect-in-handle/+page.server.js new file mode 100644 index 000000000000..86a0bb6c6242 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/actions/redirect-in-handle/+page.server.js @@ -0,0 +1,6 @@ +/** @type {import('./$types').Actions} */ +export const actions = { + default: async () => { + throw new Error('should never get here'); + } +}; diff --git a/packages/kit/test/apps/basics/src/routes/actions/redirect-in-handle/+page.svelte b/packages/kit/test/apps/basics/src/routes/actions/redirect-in-handle/+page.svelte new file mode 100644 index 000000000000..7bcb55f72822 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/actions/redirect-in-handle/+page.svelte @@ -0,0 +1,7 @@ + + +
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 3e242b9e73d9..6fb1d89ce00c 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -1042,6 +1042,29 @@ test.describe('Actions', () => { expect(page.url()).toContain('/actions/enhance'); }); + test('redirect in handle', async ({ page, javaScriptEnabled }) => { + await page.goto('/actions/redirect-in-handle'); + + page.click('button'); + + const [redirect] = await Promise.all([ + page.waitForResponse('/actions/redirect-in-handle'), + page.waitForNavigation() + ]); + if (javaScriptEnabled) { + expect(await redirect.json()).toEqual({ + type: 'redirect', + location: '/actions/enhance', + status: 303 + }); + } else { + expect(redirect.status()).toBe(303); + expect(redirect.headers()['location']).toBe('/actions/enhance'); + } + + expect(page.url()).toContain('/actions/enhance'); + }); + test('$page.status reflects error status', async ({ page, app }) => { await page.goto('/actions/enhance');