Skip to content

breaking: remove experimental.handleRenderingErrors flag - #16265

Merged
Rich-Harris merged 12 commits into
version-3from
remove-rendering-error-flag
Jul 8, 2026
Merged

breaking: remove experimental.handleRenderingErrors flag#16265
Rich-Harris merged 12 commits into
version-3from
remove-rendering-error-flag

Conversation

@Rich-Harris

Copy link
Copy Markdown
Member

Removes the flag, as this is the default behaviour in SvelteKit 3


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:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

@pkg-svelte-dev

pkg-svelte-dev Bot commented Jul 7, 2026

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from fa432ff:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/fa432ff20a8e4c72673143933ae4f6baff3ac03c

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/16265

@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fa432ff

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 Major

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

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Additional Suggestion:

Docs still instruct users to enable the removed experimental.handleRenderingErrors flag, which now throws a hard config-validation error.

Fix on Vercel

@svelte-docs-bot

Copy link
Copy Markdown

@Rich-Harris

Copy link
Copy Markdown
Member Author

gah — this is tricky. transformError is an async function, because handleError might be async. but if transformError returns a Promise then Svelte throws an error if experimental.async isn't enabled. I think this means we need to

  1. keep this under an experimental flag for now, which would really suck (we badly need this feature)
  2. disallow async handleError client hooks unless experimental.async is enabled

Comment thread packages/kit/src/runtime/server/page/render.js Outdated
vercel Bot and others added 3 commits July 7, 2026 22:49
… the error status because the reworked `transformError` no longer reassigns the local `status` variable

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

## Bug

In `packages/kit/src/runtime/server/page/render.js`, `status` is a mutable destructured parameter of `render_response` (line 59). It is consumed in two places:

- Hydration payload (line 512): `if (status !== 200 && !error) hydrate.push(...)`
- The final HTTP response (line 670): `text(transformed, { status, headers })`

The **base** implementation's `transformError` callback reassigned this closure-scoped variable when an error was caught:

```js
props.page.status = status = transformed.status;
```

The **reworked** callback (commit `2718a1d`) only updated `props.page.status` in both the sync and async branches, dropping the `status = ...` assignment:

```js
props.page.status = error.status; // local `status` never updated
```

## Trigger / failure mode

A page whose `load` succeeds (so `render_response` is invoked with `status: 200`) but whose Svelte component **throws during rendering** and is caught by an error boundary. `transformError` runs, produces e.g. a 500 error object, sets `props.page.status = 500`, but the local `status` stays `200`. The response is then built with `text(transformed, { status: 200 })`, so the client receives an HTTP `200 OK` containing the error page instead of the correct `500`. This is a behavioral regression from the base implementation.

## Fix

Restore the local reassignment in both the sync and async branches:

```js
props.page.status = status = error.status;
```

This keeps the HTTP status code (and the hydration `status` field) in sync with the error status, matching the previous behavior. I left `props.error` unchanged, as the boundary renders the error via the `failed` snippet using the `transformError` return value rather than the root `error` prop.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich-Harris <hello@rich-harris.dev>
@Rich-Harris

Copy link
Copy Markdown
Member Author

I went with 'don't wrap sync handleError hooks in an async function', which means that the await_invalid error doesn't happen if your hook is synchronous. If you do hit the error, it's a little cryptic, but I'm honestly not sure how best to address that other than docs

@dummdidumm dummdidumm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One question otherwise LGTM. Update: Nevermind, I should've read the comments before reviewing. But I've gotta say it's somewhat of a big ask - "hey you have an async handleError on the server, you gotta add async: true now whether you want to or not".

Comment thread documentation/docs/30-advanced/25-errors.md
Comment thread documentation/docs/30-advanced/25-errors.md
Comment thread packages/kit/src/core/sync/write_root.js
Comment thread packages/kit/src/core/sync/write_root.js
Comment thread packages/kit/src/runtime/server/page/render.js
Comment thread documentation/docs/30-advanced/25-errors.md Outdated
@Rich-Harris

Copy link
Copy Markdown
Member Author

But I've gotta say it's somewhat of a big ask

Existing async hooks will continue to work for the errors they work for today (i.e. load errors etc). For render errors it'll now fail somewhat gracefully, by returning a generic error and printing a 'please enable the flag' warning

Comment thread packages/kit/src/runtime/server/utils.js
vercel Bot and others added 2 commits July 8, 2026 18:17
…ch can cause an unhandled promise rejection that crashes the server

This commit fixes the issue reported at packages/kit/src/runtime/server/utils.js:122

## Bug

In `handle_error_and_jsonify` (`packages/kit/src/runtime/server/utils.js`), when the user's `handleError` hook returns a `Promise` but the app is **not** running with `compilerOptions.experimental.async` enabled and the error occurs during rendering (`!__SVELTEKIT_SUPPORTS_ASYNC__ && state.is_in_render`), the function warns and returns a generic error object:

```js
if (result instanceof Promise) {
	if (!__SVELTEKIT_SUPPORTS_ASYNC__ && state.is_in_render) {
		console.warn(...);
		return { status, message: 'Internal Error' };  // <-- `result` promise discarded
	}
	...
}
```

The `result` promise is **discarded without being awaited or given a `.catch()` handler**.

`with_request_store` (`packages/kit/src/exports/internal/server/event.js`) only wraps the call in `try/finally` — **not** `try/catch` — so it passes the promise straight through without attaching any rejection handler:

```js
export function with_request_store(store, fn) {
	try {
		sync_store = store;
		return als ? als.run(store, fn) : fn();
	} finally {
		if (!IN_WEBCONTAINER) sync_store = null;
	}
}
```

Nothing else along the path handles it.

## Concrete trigger

1.  User defines an **async** `handleError` hook that can reject (e.g. an `await`ed call to an external logging/Sentry service that throws).
2.  `compilerOptions.experimental.async` is **not** enabled — exactly the misconfiguration this branch warns about.
3.  A rendering error occurs → `handle_error_and_jsonify` is reached with `state.is_in_render === true` and the hook returns a promise.
4.  The warn branch returns the generic object and drops `result`.
5.  When `result` later rejects, there is no handler → **unhandled promise rejection**. Under Node.js v15+ default behavior (`--unhandled-rejections=throw`), this can terminate the server process.

## Fix

Attach a no-op `.catch(() => {})` to the discarded promise before returning, preventing the unhandled rejection:

```js
// we're discarding the result, but we still need to prevent an unhandled
// rejection if the user's async `handleError` hook rejects
result.catch(() => {});

return { status, message: 'Internal Error' };
```

This matches the codebase's existing convention for intentionally-ignored promises (`event.js:14`, `core/postbuild/queue.js:20`, `app/server/remote/requested.js:263`).


Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich-Harris <hello@rich-harris.dev>
@Rich-Harris
Rich-Harris merged commit fa78efb into version-3 Jul 8, 2026
18 checks passed
@Rich-Harris
Rich-Harris deleted the remove-rendering-error-flag branch July 8, 2026 20:01
Rich-Harris pushed a commit that referenced this pull request Jul 14, 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 version-3, this PR
will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

`version-3` is currently in **pre mode** so this branch has prereleases
rather than normal releases. If you want to exit prereleases, run
`changeset pre exit` on `version-3`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## @sveltejs/kit@3.0.0-next.8

### Major Changes

- breaking: remove `experimental.handleRenderingErrors` flag
([#16265](#16265))

- breaking: make `getRequest` and `setResponse` synchronous
([#16280](#16280))

- breaking: make `page.url` immutable on a type level
([#16256](#16256))

- breaking: add `refreshAll` and deprecate `invalidateAll`
([#16289](#16289))

### Minor Changes

- feat: allow hyphens in param and matcher names
([#16284](#16284))

- feat: add `ErrorProps` to generated types
([#16272](#16272))

### Patch Changes

- fix: detect destructured `load` and `actions` exports during type
generation ([#16329](#16329))

- fix: ensure CSS URL references are absolute when `paths.relative` is
`false` ([#16315](#16315))

- fix: exclude deleted cookies from `cookies.getAll()` so it stays
consistent with `cookies.get()`
([#16297](#16297))

- fix: reset failed `<svelte:boundary>` on client navigation so a stale
`+error.svelte` is torn down
([#16296](#16296))

- fix: preserve shared client chunk hashes when the app version changes
([#16324](#16324))

- fix: align MAX_COOKIE_SIZE with RFC 6265bis
([#16322](#16322))

- fix: use mouseover+mousemove for preloading to reduce events
([#16325](#16325))
## @sveltejs/package@3.0.0-next.2

### Minor Changes

- feat: warn when using a `.server.` file or file inside a `server`
directory without importing a server-only module
([#16266](#16266))
## @sveltejs/adapter-auto@8.0.0-next.1

### Patch Changes

- fix: allow prerelease versions of SvelteKit 3 to satisfy the peer
dependency range ([#16286](#16286))
- Updated dependencies
[[`737d119`](737d119),
[`fa78efb`](fa78efb),
[`07c207e`](07c207e),
[`a47071b`](a47071b),
[`14d7d5a`](14d7d5a),
[`5c38e51`](5c38e51),
[`0702baa`](0702baa),
[`e1938c6`](e1938c6),
[`8293144`](8293144),
[`f76d7d9`](f76d7d9),
[`ab5c253`](ab5c253),
[`b557b1b`](b557b1b),
[`4a513e2`](4a513e2)]:
  - @sveltejs/kit@3.0.0-next.8
## @sveltejs/adapter-cloudflare@8.0.0-next.2

### Patch Changes

- fix: allow prerelease versions of SvelteKit 3 to satisfy the peer
dependency range ([#16286](#16286))
- Updated dependencies
[[`737d119`](737d119),
[`fa78efb`](fa78efb),
[`07c207e`](07c207e),
[`a47071b`](a47071b),
[`14d7d5a`](14d7d5a),
[`5c38e51`](5c38e51),
[`0702baa`](0702baa),
[`e1938c6`](e1938c6),
[`8293144`](8293144),
[`f76d7d9`](f76d7d9),
[`ab5c253`](ab5c253),
[`b557b1b`](b557b1b),
[`4a513e2`](4a513e2)]:
  - @sveltejs/kit@3.0.0-next.8
## @sveltejs/adapter-netlify@7.0.0-next.3

### Patch Changes

- fix: include `utils.js` in package.json `files`
([#16298](#16298))
- Updated dependencies
[[`737d119`](737d119),
[`fa78efb`](fa78efb),
[`07c207e`](07c207e),
[`a47071b`](a47071b),
[`14d7d5a`](14d7d5a),
[`5c38e51`](5c38e51),
[`0702baa`](0702baa),
[`e1938c6`](e1938c6),
[`8293144`](8293144),
[`f76d7d9`](f76d7d9),
[`ab5c253`](ab5c253),
[`b557b1b`](b557b1b),
[`4a513e2`](4a513e2)]:
  - @sveltejs/kit@3.0.0-next.8
## @sveltejs/adapter-node@6.0.0-next.3

### Patch Changes

- fix: allow prerelease versions of SvelteKit 3 to satisfy the peer
dependency range ([#16286](#16286))
- Updated dependencies
[[`737d119`](737d119),
[`fa78efb`](fa78efb),
[`07c207e`](07c207e),
[`a47071b`](a47071b),
[`14d7d5a`](14d7d5a),
[`5c38e51`](5c38e51),
[`0702baa`](0702baa),
[`e1938c6`](e1938c6),
[`8293144`](8293144),
[`f76d7d9`](f76d7d9),
[`ab5c253`](ab5c253),
[`b557b1b`](b557b1b),
[`4a513e2`](4a513e2)]:
  - @sveltejs/kit@3.0.0-next.8
## @sveltejs/adapter-static@4.0.0-next.1

### Patch Changes

- fix: allow prerelease versions of SvelteKit 3 to satisfy the peer
dependency range ([#16286](#16286))
- Updated dependencies
[[`737d119`](737d119),
[`fa78efb`](fa78efb),
[`07c207e`](07c207e),
[`a47071b`](a47071b),
[`14d7d5a`](14d7d5a),
[`5c38e51`](5c38e51),
[`0702baa`](0702baa),
[`e1938c6`](e1938c6),
[`8293144`](8293144),
[`f76d7d9`](f76d7d9),
[`ab5c253`](ab5c253),
[`b557b1b`](b557b1b),
[`4a513e2`](4a513e2)]:
  - @sveltejs/kit@3.0.0-next.8
## @sveltejs/adapter-vercel@7.0.0-next.2

### Patch Changes

- fix: allow prerelease versions of SvelteKit 3 to satisfy the peer
dependency range ([#16286](#16286))
- Updated dependencies
[[`737d119`](737d119),
[`fa78efb`](fa78efb),
[`07c207e`](07c207e),
[`a47071b`](a47071b),
[`14d7d5a`](14d7d5a),
[`5c38e51`](5c38e51),
[`0702baa`](0702baa),
[`e1938c6`](e1938c6),
[`8293144`](8293144),
[`f76d7d9`](f76d7d9),
[`ab5c253`](ab5c253),
[`b557b1b`](b557b1b),
[`4a513e2`](4a513e2)]:
  - @sveltejs/kit@3.0.0-next.8

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

Development

Successfully merging this pull request may close these issues.

3 participants