Skip to content

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

Merged
dummdidumm merged 24 commits intomainfrom
error-boundaries-server
Mar 10, 2026
Merged

feat: allow error boundaries to catch errors on the server#15308
dummdidumm merged 24 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? -> answer: not pass, but update it on error
  • 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: bb79369

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


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>
@Rich-Harris
Copy link
Member

very excited for this, and even more excited to merge it into version-3 then delete a whole bunch of it

Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
@dummdidumm dummdidumm merged commit a85396c into main Mar 10, 2026
27 of 28 checks passed
@dummdidumm dummdidumm deleted the error-boundaries-server branch March 10, 2026 14:16
@github-actions github-actions bot mentioned this pull request Mar 10, 2026
elliott-with-the-longest-name-on-github pushed a commit that referenced this pull request Mar 11, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @sveltejs/kit@2.54.0

### Minor Changes

- feat: allow error boundaries to catch errors on the server
([#15308](#15308))

### Patch Changes

- chore: upgrade `devalue`
([#15535](#15535))


- fix: don't wait for remote functions that are not awaited in the
template ([#15280](#15280))


- feat: allow `resolve()` to accept pathnames with a search string
and/or hash ([#15458](#15458))


- chore: remove deprecation warnings for `config.kit.files.*` options
when validating the Svelte config file
([#15482](#15482))


- fix: handles form target attribute in remote form redirects
([#15457](#15457))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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