Skip to content

Commit 5ec2276

Browse files
authored
Merge branch 'main' into fix-custom-base-fetch
2 parents 295b2b6 + 159aece commit 5ec2276

File tree

8 files changed

+65
-14
lines changed

8 files changed

+65
-14
lines changed

.changeset/foo-bar-baz.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
@sveltejs/enhanced-img: patch
2+
'@sveltejs/enhanced-img': patch
33
---
44

55
fix: replace erroneous `import.meta.DEV` with `import.meta.env.DEV` in generated code

.changeset/quiet-swans-render.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: avoid triggering `handleError` when redirecting in a remote function

documentation/docs/20-core-concepts/10-routing.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,36 @@ A `+page.svelte` component defines a page of your app. By default, pages are ren
3939

4040
> [!NOTE] SvelteKit uses `<a>` elements to navigate between routes, rather than a framework-specific `<Link>` component.
4141
42-
Pages can receive data from `load` functions via the `data` prop. They also receive `params`, which is typed based on the route parameters.
42+
Pages can receive data from `load` functions via the `data` prop.
4343

4444
```svelte
4545
<!--- file: src/routes/blog/[slug]/+page.svelte --->
4646
<script>
4747
/** @type {import('./$types').PageProps} */
48-
let { data, params } = $props();
48+
let { data } = $props();
4949
</script>
5050
51-
<span>blog/{params.slug}</span>
52-
5351
<h1>{data.title}</h1>
5452
<div>{@html data.content}</div>
5553
```
5654

55+
As of 2.24, pages also receive a `params` prop which is typed based on the route parameters. This is particularly useful alongside [remote functions](remote-functions):
56+
57+
```svelte
58+
<!--- file: src/routes/blog/[slug]/+page.svelte --->
59+
<script>
60+
import { getPost } from '../blog.remote';
61+
62+
/** @type {import('./$types').PageProps} */
63+
let { params } = $props();
64+
65+
const post = $derived(await getPost(params.slug));
66+
</script>
67+
68+
<h1>{post.title}</h1>
69+
<div>{@html post.content}</div>
70+
```
71+
5772
> [!LEGACY]
5873
> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` property manually with `PageData` instead, see [$types](#\$types).
5974
>

documentation/docs/20-core-concepts/60-remote-functions.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,22 +190,22 @@ import * as v from 'valibot';
190190
import { query } from '$app/server';
191191
import * as db from '$lib/server/database';
192192

193-
export const getWeather = query.batch(v.string(), async (cities) => {
193+
export const getWeather = query.batch(v.string(), async (cityIds) => {
194194
const weather = await db.sql`
195195
SELECT * FROM weather
196-
WHERE city = ANY(${cities})
196+
WHERE city_id = ANY(${cityIds})
197197
`;
198-
const lookup = new Map(weather.map(w => [w.city, w]));
198+
const lookup = new Map(weather.map(w => [w.city_id, w]));
199199

200-
return (city) => lookup.get(city);
200+
return (cityId) => lookup.get(cityId);
201201
});
202202
```
203203
204204
```svelte
205205
<!--- file: Weather.svelte --->
206206
<script>
207207
import CityWeather from './CityWeather.svelte';
208-
import { getWeather } from './weather.remote.js';
208+
import { getWeather } from './weather.remote';
209209

210210
let { cities } = $props();
211211
let limit = $state(5);

packages/kit/src/runtime/server/page/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { text } from '@sveltejs/kit';
2-
import { Redirect } from '@sveltejs/kit/internal';
2+
import { HttpError, Redirect } from '@sveltejs/kit/internal';
33
import { compact } from '../../../utils/array.js';
44
import { get_status, normalize_error } from '../../../utils/error.js';
55
import { add_data_suffix } from '../../pathname.js';
@@ -357,6 +357,11 @@ export async function render_page(
357357
ssr === false ? server_data_serializer(event, event_state, options) : data_serializer
358358
});
359359
} catch (e) {
360+
// a remote function could have thrown a redirect during render
361+
if (e instanceof Redirect) {
362+
return redirect_response(e.status, e.location);
363+
}
364+
360365
// if we end up here, it means the data loaded successfully
361366
// but the page failed to render, or that a prerendering error occurred
362367
return await respond_with_error({
@@ -365,7 +370,7 @@ export async function render_page(
365370
options,
366371
manifest,
367372
state,
368-
status: 500,
373+
status: e instanceof HttpError ? e.status : 500,
369374
error: e,
370375
resolve_opts
371376
});

packages/kit/src/types/internal.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export class InternalServer extends Server {
176176
request: Request,
177177
options: RequestOptions & {
178178
prerendering?: PrerenderOptions;
179-
read: (file: string) => Buffer;
179+
read: (file: string) => NonSharedBuffer;
180180
/** A hook called before `handle` during dev, so that `AsyncLocalStorage` can be populated. */
181181
before_handle?: (event: RequestEvent, config: any, prerender: PrerenderOption) => void;
182182
emulator?: Emulator;
@@ -529,7 +529,7 @@ export interface SSRState {
529529
* prerender option is inherited by the endpoint, unless overridden.
530530
*/
531531
prerender_default?: PrerenderOption;
532-
read?: (file: string) => Buffer;
532+
read?: (file: string) => NonSharedBuffer;
533533
/**
534534
* Used to set up `__SVELTEKIT_TRACK__` which checks if a used feature is supported.
535535
* E.g. if `read` from `$app/server` is used, it checks whether the route's config is compatible.

packages/kit/test/apps/async/src/hooks.server.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import { isRedirect } from '@sveltejs/kit';
2+
13
/** @type {import('@sveltejs/kit').HandleValidationError} */
24
export const handleValidationError = ({ issues }) => {
35
return { message: issues[0].message };
46
};
57

68
/** @type {import('@sveltejs/kit').HandleServerError} */
79
export const handleError = ({ error: e, status, message }) => {
10+
// helps us catch sveltekit redirects thrown in component code
11+
if (isRedirect(e)) {
12+
throw new Error("Redirects shouldn't trigger the handleError hook");
13+
}
14+
815
const error = /** @type {Error} */ (e);
916

1017
return { message: `${error.message} (${status} ${message})` };

packages/kit/test/apps/async/test/test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import http from 'node:http';
12
import { expect } from '@playwright/test';
23
import { test } from '../../../utils.js';
34

@@ -25,6 +26,24 @@ test.describe('remote functions', () => {
2526
await expect(page.locator('#redirected')).toHaveText('redirected');
2627
});
2728

29+
test("query that's awaited and throws a redirect doesn't trigger handleError hook", async ({
30+
baseURL
31+
}) => {
32+
const { status, location } = await new Promise((fulfil, reject) => {
33+
const request = http.get(`${baseURL}/remote/query-redirect/from-page`, (response) => {
34+
fulfil({
35+
status: response.statusCode,
36+
location: response.headers.location
37+
});
38+
response.resume();
39+
});
40+
request.on('error', reject);
41+
});
42+
43+
expect(status).toBe(307);
44+
expect(location).toBe('/remote/query-redirect/redirected');
45+
});
46+
2847
test('non-exported queries do not clobber each other', async ({ page }) => {
2948
await page.goto('/remote/query-non-exported');
3049

0 commit comments

Comments
 (0)