Skip to content

feat: allow error boundaries to catch errors on the server#15308

Open
dummdidumm wants to merge 22 commits intomainfrom
error-boundaries-server
Open

feat: allow error boundaries to catch errors on the server#15308
dummdidumm wants to merge 22 commits intomainfrom
error-boundaries-server

Conversation

@dummdidumm
Copy link
Member

@dummdidumm dummdidumm commented Feb 12, 2026

Take advantage of sveltejs/svelte#17672 to add the handleError hook as transformError so that error boundaries run on the server. Behind an experimental flag.

Closes #14808
Closes #14410
Closes #14398
Closes #14932

New tests fail right now, install the other PR via pkg.new locally to see it in effect.

Todos:

  • what about page.status, should it be passed as a prop to +error.svelte, too? What would it be in case of a rendering error though?
  • what about the navigation event on the client? handleError receives one, but for render errors we don't have this context. Do a best effort of a stub, like "this was the URL this happened on" etc? Or just pass null? Solved: We can use the last navigation event we know of
  • a few others (see code)

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Take advantage of sveltejs/svelte#17672 to add the `handleError` hook as `transformError` so that error boundaries run on the server. Behind an experimental flag.

Closes #14808
Closes #14410
Closes #14398
@changeset-bot
Copy link

changeset-bot bot commented Feb 12, 2026

🦋 Changeset detected

Latest commit: 4546cb3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svelte-docs-bot
Copy link

remoteFunctions: false,
forkPreloads: false
forkPreloads: false,
serverErrorBoundaries: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a great alternative suggestion but I feel like serverErrorBoundaries doesn't totally cover it, since this also affects the behaviour of boundaries client-side


if (errors && __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__) {
let last_idx = -1;
result.props.errors = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in an ideal world, we wouldn't need to do all this — the error boundary self-activate. that would involve turning load errors into render errors. how achievable does that sound? (if the answer is 'lol not at all' then that's fine)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...maybe by making the data prop a getter that throws? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what you mean by this. This is about loading the +error.svelte components. We have to do this here upfront because we can't rely on using await in the template - it's still experimental and will be for a while.


/** @type {Array<import('types').SSRComponent | undefined> | undefined} */
let error_components;
if (options.server_error_boundaries && ssr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dummdidumm added a commit to sveltejs/svelte that referenced this pull request Feb 24, 2026
This will get the tests in sveltejs/kit#15308 green, right now they fail because page state cannot be found because context not available
Rich-Harris pushed a commit to sveltejs/svelte that referenced this pull request Feb 25, 2026
This will get the tests in sveltejs/kit#15308
green, right now they fail because page state cannot be found because
context not available
@dummdidumm dummdidumm marked this pull request as ready for review February 26, 2026 18:38
vercel bot and others added 3 commits February 26, 2026 23:31
… double colons instead of single colon.

This commit fixes the issue reported at documentation/docs/30-advanced/25-errors.md:149

**Bug Explanation:**
In the SvelteKit documentation file `documentation/docs/30-advanced/25-errors.md`, there is a typo in a code example demonstrating the use of `<svelte:boundary>` for error handling. The closing tag was written as `</svelte::boundary>` (with two colons) instead of `</svelte:boundary>` (with one colon). This is inconsistent with the opening tag `<svelte:boundary>` which correctly uses a single colon.

This typo would confuse developers reading the documentation, as it shows invalid Svelte syntax. Users copying this code example would get a syntax error when trying to use it.

**Fix Explanation:**
Changed the closing tag from `</svelte::boundary>` to `</svelte:boundary>` to match the opening tag and use the correct Svelte special element syntax.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: dummdidumm <sholthausen@web.de>
…after `error` has been reassigned to the transformed `App.Error`, causing the original HTTP status to always be replaced with 500.

This commit fixes the issue reported at packages/kit/src/runtime/server/page/render.js:194

## Bug Explanation

The bug is located in `packages/kit/src/runtime/server/page/render.js` in the `transformError` callback (line 194).

### Why it happens:

1. The `transformError` callback receives `e` as a parameter - this is the original error which could be an `HttpError` (e.g., from `error(403, 'Forbidden')`) or a `SvelteKitError`, both of which have a `.status` property.

2. The callback calls `handle_error_and_jsonify(event, event_state, options, e)` which transforms the error into an `App.Error` object. This `App.Error` is NOT an `HttpError` or `SvelteKitError`.

3. The code then reassigns: `props.page.error = props.error = error = transformed;`

4. The bug: `props.page.status = status = get_status(error);` - This calls `get_status()` on the transformed error (now an `App.Error`), NOT the original error.

5. The `get_status()` function (in `packages/kit/src/utils/error.js`) checks: `error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500`

6. Since the transformed `App.Error` is neither `HttpError` nor `SvelteKitError`, `get_status()` always returns 500.

### Impact:

When a component throws `error(403, 'Forbidden')` or any other HTTP error during rendering (when `handleRenderingErrors` is enabled), the 403 status would be lost and replaced with 500. This affects the HTTP response status code and the page's status property.

## Fix Explanation

Changed `get_status(error)` to `get_status(e)` on line 194.

This ensures we extract the status from the original error `e` (which may be an `HttpError` or `SvelteKitError` with the correct status) rather than the transformed error which is always `App.Error`.

The fix is minimal and targeted - it only changes the argument passed to `get_status()` to use the correct variable that still contains the original error with its status information.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: dummdidumm <sholthausen@web.de>
…with 500 when errors are transformed in the client-side `transformError` callback because `get_status()` is called on the transformed `App.Error` instead of the original error.

This commit fixes the issue reported at packages/kit/src/runtime/client/client.js:622

## Bug Explanation

The bug exists in the client-side `transformError` callback when the experimental error handling feature is enabled.

### How it happens:

1.  When an error is thrown (e.g., `error(403, 'Forbidden')`), it's passed to `transformError` as parameter `e`
2.  The error is transformed via `handle_error(e, ...)` which returns an `App.Error` object (a user-friendly error representation)
3.  `get_status(error)` is then called on the **transformed** error to set the HTTP status
4.  However, `get_status()` (in `packages/kit/src/utils/error.js`) only extracts the correct status from `HttpError` or `SvelteKitError` instances - for any other type, it returns 500
5.  Since the transformed error is an `App.Error` (plain object), `get_status()` always returns 500, losing the original status

### Impact:

If a user throws `error(403, 'Forbidden')` in a component, the page would incorrectly show status 500 instead of 403. This breaks the expected behavior where `$page.status` should reflect the original error status.

## Fix Explanation

The fix changes `get_status(error)` to `get_status(e)` in the client-side code (`packages/kit/src/runtime/client/client.js` line 621):

*   Before: `rendering_error = { error, status: get_status(error) };`
*   After: `rendering_error = { error, status: get_status(e) };`

By calling `get_status(e)` on the **original** error (parameter `e`) before transformation, we correctly extract the HTTP status from the `HttpError` or `SvelteKitError` instance, preserving the intended status code (403, 404, etc.).

Note: The server-side fix in `render.js` was already applied in a separate commit.


Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: dummdidumm <sholthausen@web.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants