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
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ You can read more about omitting `$types` in our [blog post](/blog/zero-config-t

Any other files inside a route directory are ignored by SvelteKit. This means you can colocate components and utility modules with the routes that need them.

Files such as +page.test.js, +server.spec.ts and +page.stories.svelte are also ignored, allowing tests and stories to be colocated with route files.

If components and modules are needed by multiple routes, it's a good idea to put them in [`#lib`]($lib).

## Further reading
Expand Down
4 changes: 2 additions & 2 deletions apps/svelte.dev/content/docs/kit/20-core-concepts/20-load.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ export async function load({ params, parent }) {

## Errors

If an error is thrown during `load`, the nearest [`+error.svelte`](routing#error) will be rendered. For [_expected_](errors#Expected-errors) errors, use the `error` helper from `@sveltejs/kit` to specify the HTTP status code and an optional message:
If an error is thrown during `load`, the nearest [`+error.svelte`](routing#error) will be rendered. For [app errors](errors#App-errors), use the `error` helper from `@sveltejs/kit` to specify the HTTP status code and an optional message:

```js
/// file: src/routes/admin/+layout.server.js
Expand Down Expand Up @@ -454,7 +454,7 @@ export function load({ locals }) {

Calling `error(...)` will throw an exception, making it easy to stop execution from inside helper functions.

If an [_unexpected_](errors#Unexpected-errors) error is thrown, SvelteKit will invoke [`handleError`](hooks#handleError) and treat it as a 500 Internal Error.
Every error is passed to the [`handleError`](hooks#handleError) hook. An [unknown error](errors#Unknown-errors) is treated as a 500 Internal Error unless the hook says otherwise.

> [!NOTE] [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the error yourself

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ We can also implement progressive enhancement ourselves, without `use:enhance`,
body: data
});

/** @type {import('@sveltejs/kit').ActionResult} */
/** @type {import('$app/forms').ActionResult} */
const result = deserialize(await response.text());

if (result.type !== 'redirect' && result.location !== undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1237,18 +1237,23 @@ As long as _you're_ not passing invalid data to your remote functions, there are
- the function signature changed between deployments, and some users are currently on an older version of your app
- someone is trying to attack your site by poking your exposed endpoints with bad data

In the second case, we don't want to give the attacker any help, so SvelteKit will generate a generic [400 Bad Request](https://http.dog/400) response. You can control the message by implementing the [`handleValidationError`](hooks#handleValidationError) server hook, which, like [`handleError`](hooks#handleError), must return an [`App.Error`](errors#Type-safety) (which defaults to `{ message: string }`):
In the second case, we don't want to give the attacker any help, so SvelteKit will generate a generic [400 Bad Request](https://http.dog/400) response. Validation failures pass through the server [`handleError`](hooks#handleError) hook with `kind: 'validation'`, an `error` object containing `{ status, message }`, and the validation `issues`. You can use the issues to log the failure or customise the response:

```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function handleValidationError({ event, issues }) {
return {
message: 'Nice try, hacker!'
};
/** @type {import('@sveltejs/kit').HandleServerError} */
export function handleError({ kind, issues }) {
if (kind === 'validation') {
console.error(issues);
return {
message: 'Nice try, hacker!'
};
}
}
```

Be thoughtful about exposing validation issues, as they may give an attacker useful information. Returning `error` unchanged is safe — unlike `issues`, it only contains the generic status and message.

If you know what you're doing and want to opt out of validation, you can pass the string `'unchecked'` in place of a schema:

```ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function load(event) {
}
```

> [!NOTE] If you don't handle 404 cases, they will appear in [`handleError`](hooks#handleError)
> [!NOTE] If you don't handle 404 cases, they will appear in [`handleError`](hooks#handleError) as [framework errors](errors#Framework-errors), with `kind: 'framework'`. Otherwise, they will appear with `kind: 'app'`.

## Optional parameters

Expand All @@ -76,7 +76,7 @@ A route like `src/routes/fruits/[page]` would match `/fruits/apple`, but it woul

```js
/// file: src/params.js
import { defineParams } from '@sveltejs/kit';
import { defineParams } from '@sveltejs/kit/params';

export const params = defineParams({
fruit: (param) => {
Expand All @@ -98,7 +98,7 @@ You can also use a [Standard Schema](https://standardschema.dev) — for example

```js
/// file: src/params.js
import { defineParams } from '@sveltejs/kit';
import { defineParams } from '@sveltejs/kit/params';
import * as v from 'valibot';

export const params = defineParams({
Expand Down
91 changes: 50 additions & 41 deletions apps/svelte.dev/content/docs/kit/30-advanced/20-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,59 +144,46 @@ export async function handleFetch({ event, request, fetch }) {
}
```

## handleValidationError

> [!NOTE] Can be added to `src/hooks.server.js`

This hook is called when a remote function is called with an argument that does not match the provided [Standard Schema](https://standardschema.dev/). It must return an object matching the shape of [`App.Error`](types#Error).

Say you have a remote function that expects a string as its argument ...

```js
/// file: todos.remote.js
import * as v from 'valibot';
import { query } from '$app/server';

export const getTodo = query(v.string(), (id) => {
// implementation...
});
```
## handleError

...but it is called with something that doesn't match the schema — such as a number (e.g. `await getTodos(1)`) — then validation will fail, the server will respond with a [400 status code](https://http.dog/400), and the function will throw with the message 'Bad Request'.
> [!NOTE] Can be added to `src/hooks.server.js` and `src/hooks.client.js`

To customise this message and add additional properties to the error object, implement `handleValidationError`:
This function is called for _every_ error thrown while loading, rendering, or responding to a request. This allows for two things:

```js
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleValidationError} */
export function handleValidationError({ issues }) {
return {
message: 'No thank you'
};
}
```
- you can log the error
- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value becomes the value of `page.error`.

Be thoughtful about what information you expose here, as the most likely reason for validation to fail is that someone is sending malicious requests to your server.
Alongside the `event`, the hook receives a `kind` discriminant that tells you where the error came from, and the `error` itself:

## handleError
- `'app'` — the error came from your app via [`error(...)`](@sveltejs-kit#error)
- `error` is the error body, which matches [`App.Error`](types#Error)
- defaults to the error body itself
- `'framework'` — the error came from SvelteKit, such as a 404, 405 or 413
- `error` is `{ status, message }`, where `message` is safe text like `Not Found`
- defaults to that same `{ status, message }`
- `'validation'` (server only) — the error came from validating a remote function argument against its [Standard Schema](https://standardschema.dev/)
- `error` is `{ status: 400, message: 'Bad Request' }`, and `issues` contains the validation issues
- defaults to the `error` object; the issues are not exposed unless you explicitly return them
- to access validation-library-specific issue properties, parameterise `HandleServerError` with the issue type, for example `HandleServerError<CustomIssue>`
- `'unknown'` — we don't know what went wrong; the error was thrown by your code, or code it calls
- `error` is the thrown value, which may contain information unsafe to expose
- defaults to `{ status: 500, message: 'Internal Error' }`

> [!NOTE] Can be added to `src/hooks.server.js` and `src/hooks.client.js`
The next section, [Errors](errors), explains the other categories in more detail. Redirects are not errors, and never reach the hook.

If an [unexpected error](errors#Unexpected-errors) is thrown during loading, rendering, or from an endpoint, this function will be called with the `error`, `event`, `status` code and `message`. This allows for two things:
The hook returns an object matching [`App.Error`](types#Error), in which `status` and `message` are optional — return them only to override the defaults in the list above.

- you can log the error
- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value, which defaults to `{ message }`, becomes the value of `page.error`.
> [!NOTE] If you augment `App.Error` with additional _required_ properties, the hook must return them.

For errors thrown from your code (or library code called by your code) the status will be 500 and the message will be "Internal Error". While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe (albeit meaningless to the average user).

To add more information to the `page.error` object in a type-safe way, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior). This allows you to — for example — append a tracking ID for users to quote in correspondence with your technical support staff:
To add more information to the `page.error` object in a type-safe way, augment the existing `App.Error` interface with your additional properties. The built-in `status` and `message` properties are already present and do not need to be redeclared. For example, you can add a tracking ID for users to quote when contacting support:

```ts
/// file: src/app.d.ts
declare global {
namespace App {
interface Error {
message: string;
errorId: string;
}
}
Expand All @@ -221,14 +208,28 @@ import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleServerError} */
export async function handleError({ error, event, status, message }) {
export async function handleError({ kind, error, event }) {
if (kind === 'app') {
// you created this error with `error(...)`, so it already
// matches `App.Error` — pass it through unchanged
return error;
}

const errorId = crypto.randomUUID();

if (kind === 'framework') {
// a 404 (or similar) — `error.status` and `error.message` are safe to
// expose, so we keep them and just add our own property
return { ...error, errorId };
}

// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId, status }
extra: { event, errorId }
});

// `status` and `message` are optional — we only override `message`,
// so the status stays at its default of 500
return {
message: 'Whoops!',
errorId
Expand All @@ -252,12 +253,20 @@ import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleClientError} */
export async function handleError({ error, event, status, message }) {
export async function handleError({ kind, error, event }) {
if (kind === 'app') {
return error;
}

const errorId = crypto.randomUUID();

if (kind === 'framework') {
return { ...error, errorId };
}

// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId, status }
extra: { event, errorId }
});

return {
Expand All @@ -269,7 +278,7 @@ export async function handleError({ error, event, status, message }) {

> [!NOTE] In `src/hooks.client.js`, the type of `handleError` is `HandleClientError` instead of `HandleServerError`, and `event` is a `NavigationEvent` rather than a `RequestEvent`.

This function is not called for _expected_ errors (those thrown with the [`error`](@sveltejs-kit#error) function imported from `@sveltejs/kit`).
Errors that were already transformed by the server-side hook are not passed to the client-side hook a second time.

During development, if an error occurs because of a syntax error in your Svelte code, the passed in error has a `frame` property appended highlighting the location of the error.

Expand Down
47 changes: 31 additions & 16 deletions apps/svelte.dev/content/docs/kit/30-advanced/25-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ Errors are an inevitable fact of software development. SvelteKit handles errors

## Error objects

SvelteKit distinguishes between expected and unexpected errors, both of which are represented as simple `{ status: number, message: string }` objects by default.
Every error passes through the [`handleError`](hooks#handleError) hook — which can log it and customise it — before it is rendered. The hook's `kind` property identifies where the error came from: your app (`'app'`), SvelteKit (`'framework'`), validation of a [remote function](remote-functions) argument (`'validation'`) or an unknown source (`'unknown'`). By default, all are represented as simple `{ status: number, message: string }` objects.

You can add additional properties, like a `code` or a tracking `id`, as shown in the examples below. (When using TypeScript this requires you to redefine the `Error` type as described in [type safety](errors#Type-safety)).

## Expected errors
## App errors

An _expected_ error is one created with the [`error`](@sveltejs-kit#error) helper imported from `@sveltejs/kit`:
An _app_ error is one created with the [`error`](@sveltejs-kit#error) helper imported from `@sveltejs/kit`:

```js
/// file: src/routes/blog/[slug]/+page.server.js
Expand Down Expand Up @@ -41,6 +41,8 @@ export async function load({ params }) {

This throws an exception that SvelteKit catches, causing it to set the response status code to 404 and render an [`+error.svelte`](routing#error) component, where the `error` is an `App.Error` object with the provided `status` and `message`.

On its way there, the error passes through the [`handleError`](hooks#handleError) hook with `kind: 'app'`. Since the shape of the error is determined by your app, it is considered safe to expose, and the hook can pass it through unchanged.

```svelte
<!--- file: src/routes/+error.svelte --->
<script>
Expand Down Expand Up @@ -74,39 +76,52 @@ error(404, 'Not found', {

> [!NOTE] [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the `error` yourself

## Unexpected errors
## Unknown errors

An _unexpected_ error is any other exception that occurs while handling a request. Since these can contain sensitive information, unexpected error messages and stack traces are not exposed to users.
An _unknown_ error is any other exception that occurs while handling a request. Since these can contain sensitive information, unknown error messages and stack traces are not exposed to users.

By default, unexpected errors are printed to the console (or, in production, your server logs), while the error that is exposed to the user has a generic shape:
By default, unknown errors are printed to the console (or, in production, your server logs), while the error that is exposed to the user has a generic shape:

```json
{ "status": 500, "message": "Internal Error" }
```

Unexpected errors will go through the [`handleError`](hooks#handleError) hook, where you can add your own error handling — for example, sending errors to a reporting service, or returning a custom error object which becomes the `error` prop passed to `+error.svelte`.
Unknown errors go through the [`handleError`](hooks#handleError) hook with `kind: 'unknown'`, because SvelteKit does not know what went wrong. There you can add your own error handling, for example sending errors to a reporting service, or returning a custom error object which becomes the `error` prop passed to `+error.svelte`. The value you receive is the raw thrown value, and nothing about it is exposed unless you choose to expose it.

You can override the HTTP status code used in the response by returning a `status` property:
Anything you return overrides the defaults, so you can — for example — use the type of the thrown error to determine the HTTP status code used in the response:

```js
/// file: src/hooks.server.js
// Assuming you have this ...
class NotFound extends Error {}

/** @type {import('@sveltejs/kit').HandleServerError} */
export function handleError({ error, event, status, message }) {
// ... you can do this
if (error instanceof NotFound) {
return {
status: 404,
message: 'Not found'
};
export function handleError({ kind, error, event }) {
if (kind === 'unknown') {
// ... you can do this
if (error instanceof NotFound) {
return {
status: 404,
message: 'Not found'
};
}

return { message: 'Something went wrong' };
}

return { message: 'Something went wrong' };
// app and framework errors are already safe to expose
return error;
}
```

## Framework errors

Some errors are generated by SvelteKit itself rather than by your code — a request for a page that doesn't exist (404), a `POST` request to a page without actions (405), a request body that exceeds the size limit (413), and so on.

These also go through `handleError`, with a `kind` of `'framework'`. The `error` you receive is a `{ status, message }` object whose `message` is a terse but safe description of what went wrong, such as `'Not Found'`, so it can be exposed to users as-is.

If you log errors inside `handleError`, remember that framework errors such as 404s are routine — you will usually want to gate logging on `kind === 'unknown'`.

## Error boundaries

Errors that occur during `load` or rendering (for example inside a component's `<script>` block or template) bubble up to the nearest `+error.svelte` component. To handle errors at a more granular level, you can use a [`<svelte:boundary>`](../svelte/svelte-boundary):
Expand Down
Loading