Skip to content

fix: show error.html when root layout load() throws in SPA mode#15798

Open
Zelys-DFKH wants to merge 9 commits into
sveltejs:mainfrom
Zelys-DFKH:fix/spa-root-layout-error-page
Open

fix: show error.html when root layout load() throws in SPA mode#15798
Zelys-DFKH wants to merge 9 commits into
sveltejs:mainfrom
Zelys-DFKH:fix/spa-root-layout-error-page

Conversation

@Zelys-DFKH
Copy link
Copy Markdown

closes #13721

In SPA mode (ssr: false on the root layout), when load() throws, SvelteKit calls load_root_error_page to show an error page. That function re-runs the same root layout load() to gather data for the error boundary, which throws again. The inner catch had a // TODO: SPA mode? comment and re-threw with no handler. Blank page.

The fix reads error.html at build time and bundles the content into app.js as error_template (via write_client_manifest). When the inner catch fires, it writes the template directly to the document, substituting %sveltekit.status% and %sveltekit.error.message%: the same substitution the server already does. The original throw error is preserved as a fallback if error_template is empty.

The regression test is in the existing no-ssr test app: a /root-layout-error route throws from load(), and Playwright verifies that error.html renders with the right status and message rather than a blank page.

(Thanks to @inq for independently identifying the root cause and the same approach in the issue comments.)


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.

In SPA mode, if the root layout's load() throws, load_root_error_page
re-runs that same load() to find an error URL, which throws again. The
inner catch had a // TODO: SPA mode? comment and re-threw the error
with no handler. Result: blank page.

Fix: bundle src/error.html into app.js as error_template at build time
(via the existing load_error_page helper). When the inner catch fires
and error_template is set, document.write() replaces the page with the
error HTML. Status and message placeholders are substituted, and the
message is HTML-escaped before writing.

Closes sveltejs#13721
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 4, 2026

🦋 Changeset detected

Latest commit: e4cc71b

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 Patch

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
Copy Markdown

Comment thread packages/kit/src/runtime/client/client.js Outdated
@Zelys-DFKH
Copy link
Copy Markdown
Author

🔔 Gentle ping — this PR has been ready with green CI for several days. Happy to address feedback or answer questions about the approach!

@teemingc
Copy link
Copy Markdown
Member

teemingc commented May 9, 2026

There are 6 CI checks currently failing

… false in test

document.open() fires executionContextsCleared on Windows, destroying
Playwright's CDP connection. DOMParser + adoptNode updates the DOM
in-place without resetting document state.

The test was waiting for body.started, which is structurally impossible
on this path since initialize() never runs — the error is thrown before
it. { wait_for_started: false } opts out of that wait.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Zelys-DFKH
Copy link
Copy Markdown
Author

Thanks for the nudge. My fault — the test I added was waiting for body.started, which is the CSS class SvelteKit sets after initialize() finishes. On this code path initialize() never runs (the error gets thrown before it gets there), so the test was always going to time out. Adding { wait_for_started: false } fixes it.

I also swapped out document.open() / document.write() / document.close() for DOMParser + adoptNode. The document.open() call fires executionContextsCleared on Windows, which destroys Playwright's connection to the page — and document.write() is deprecated anyway. The in-place DOM replacement avoids both problems.

The other failures in the current/beta jobs are showing up on main today too, so they're not related to this PR.

Zelys-DFKH and others added 2 commits May 9, 2026 11:53
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';

export const error_template = ${s(error_page)};
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.

I wonder if it's worth loading the error template only when it's needed rather than serialising it here (we can write a module to the .svelte-kit/generated folder and dynamically import it when we need to display the error page)

@Zelys-DFKH
Copy link
Copy Markdown
Author

Took a shot at this. Since client.js lives in the source tree rather than the generated folder, it can't resolve ./error-template.js directly. Went with a getter in app.js instead: it lives in the generated folder, so the relative path resolves correctly and Vite can follow the static string for chunking.

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.

SSR disabled incorrectly handles root layout exceptions

2 participants