diff --git a/.changeset/calm-peaches-enjoy.md b/.changeset/calm-peaches-enjoy.md new file mode 100644 index 000000000000..aafb78f214e1 --- /dev/null +++ b/.changeset/calm-peaches-enjoy.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[fix] handle redirects in handle hook while processing data request diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index 44a0fe2967f6..3b0f7e97c86f 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -117,12 +117,7 @@ export async function render_data(event, route, options, state, trailing_slash) const error = normalize_error(e); if (error instanceof Redirect) { - return json_response( - JSON.stringify({ - type: 'redirect', - location: error.location - }) - ); + return redirect_json_response(error); } else { // TODO make it clearer that this was an unexpected error return json_response(JSON.stringify(handle_error_and_jsonify(event, options, error))); @@ -143,3 +138,15 @@ function json_response(json, status = 200) { } }); } + +/** + * @param {Redirect} redirect + */ +export function redirect_json_response(redirect) { + return json_response( + JSON.stringify({ + type: 'redirect', + location: redirect.location + }) + ); +} diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index f5554c4c64cf..4b9119258d33 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -13,7 +13,7 @@ import { strip_data_suffix } from '../../utils/url.js'; import { exec } from '../../utils/routing.js'; -import { INVALIDATED_HEADER, render_data } from './data/index.js'; +import { INVALIDATED_HEADER, redirect_json_response, render_data } from './data/index.js'; import { add_cookies_to_headers, get_cookies } from './cookie.js'; import { create_fetch } from './fetch.js'; import { Redirect } from '../control.js'; @@ -289,10 +289,23 @@ export async function respond(request, options, state) { } } + // Edge case: If user does `return Response(30x)` in handle hook while processing a data request, + // we need to transform the redirect response to a corresponding JSON response. + if (is_data_request && response.status >= 300 && response.status <= 308) { + const location = response.headers.get('location'); + if (location) { + return redirect_json_response(new Redirect(/** @type {any} */ (response.status), location)); + } + } + return response; } catch (error) { if (error instanceof Redirect) { - return redirect_response(error.status, error.location); + if (is_data_request) { + return redirect_json_response(error); + } else { + return redirect_response(error.status, error.location); + } } return handle_fatal_error(event, options, error); } diff --git a/packages/kit/test/apps/basics/src/hooks.server.js b/packages/kit/test/apps/basics/src/hooks.server.js index d7541ef1d6be..2e5938aa5779 100644 --- a/packages/kit/test/apps/basics/src/hooks.server.js +++ b/packages/kit/test/apps/basics/src/hooks.server.js @@ -1,7 +1,7 @@ import fs from 'fs'; import { sequence } from '@sveltejs/kit/hooks'; import { HttpError } from '../../../../src/runtime/control'; -import { error } from '@sveltejs/kit'; +import { error, redirect } from '@sveltejs/kit'; /** * Transform an error into a POJO, by copying its `name`, `message` @@ -80,6 +80,17 @@ export const handle = sequence( } catch {} return response; + }, + async ({ event, resolve }) => { + if (event.url.pathname.includes('/redirect/in-handle')) { + if (event.url.search === '?throw') { + throw redirect(307, event.url.origin + '/redirect/c'); + } else { + return new Response(undefined, { status: 307, headers: { location: '/redirect/c' } }); + } + } + + return resolve(event); } ); diff --git a/packages/kit/test/apps/basics/src/routes/redirect/+page.svelte b/packages/kit/test/apps/basics/src/routes/redirect/+page.svelte index 70f0a624f0ff..852940e4edd0 100644 --- a/packages/kit/test/apps/basics/src/routes/redirect/+page.svelte +++ b/packages/kit/test/apps/basics/src/routes/redirect/+page.svelte @@ -7,3 +7,6 @@ a (missing-status) b (missing-status) + +in-handle (throw redirect) +in-handle (return Response) diff --git a/packages/kit/test/apps/basics/src/routes/redirect/in-handle/+page.server.js b/packages/kit/test/apps/basics/src/routes/redirect/in-handle/+page.server.js new file mode 100644 index 000000000000..23addb029828 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/redirect/in-handle/+page.server.js @@ -0,0 +1 @@ +export function load() {} diff --git a/packages/kit/test/apps/basics/src/routes/redirect/in-handle/+page.svelte b/packages/kit/test/apps/basics/src/routes/redirect/in-handle/+page.svelte new file mode 100644 index 000000000000..6a72cbc956b3 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/redirect/in-handle/+page.svelte @@ -0,0 +1 @@ +

in-handle

diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 8678d1b1a6b6..633ea3b7a880 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -1337,6 +1337,32 @@ test.describe('Redirects', () => { expect(await page.textContent('h1')).toBe('Hazaa!'); } }); + + test('redirect response in handle hook', async ({ baseURL, clicknav, page }) => { + await page.goto('/redirect'); + + await clicknav('[href="/redirect/in-handle?response"]'); + + await page.waitForURL('/redirect/c'); + expect(await page.textContent('h1')).toBe('c'); + expect(page.url()).toBe(`${baseURL}/redirect/c`); + + await page.goBack(); + expect(page.url()).toBe(`${baseURL}/redirect`); + }); + + test('throw redirect in handle hook', async ({ baseURL, clicknav, page }) => { + await page.goto('/redirect'); + + await clicknav('[href="/redirect/in-handle?throw"]'); + + await page.waitForURL('/redirect/c'); + expect(await page.textContent('h1')).toBe('c'); + expect(page.url()).toBe(`${baseURL}/redirect/c`); + + await page.goBack(); + expect(page.url()).toBe(`${baseURL}/redirect`); + }); }); test.describe('Routing', () => {