Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support for <svelte:boundary> on SSR / static generation (POC) #15462

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

adiguba
Copy link
Contributor

@adiguba adiguba commented Mar 6, 2025

I just made a PoC for #15370

Currently <svelte:boundary> works like a simple block in SSR, and any errors on the children will generate an HTTP 500 error.
So there is no way to handle template errors server side.

With this PR, if an error occur while rendering the children, it will be removed from the generated code and replaced by the failed() snippets (if any).

Example, this code :

<svelte:boundary>
	<p>children</p>

	{#snippet failed(err)}
		<pre>ERROR</pre>
	{/snippet}
</svelte:boundary>

generate a SSR code like that :

	$$payload.out += `<!--[-->`;
	{
		function failed($$payload, err) {
			$$payload.out += `<pre>ERROR</pre>`;
		}
		$$payload.out += `<p>children</p>`;
	}
	$$payload.out += `<!--]-->`;

=> failed() is ignored, and it just print the payload of the body without handling errors :(

With this PR, the generated code will be :

	boundary(
		$$payload,
		($$payload) => {
			$$payload.out += `<p>children</p>`;
		},
		($$payload, err) => {
			$$payload.out += `<pre>ERROR</pre>`;
		}
	);

The boundary function will handle errors, and the payload will be populated as expected.

On hydration, the client will detect the error on SSR and drop the content in order to retry to rendering the template.

There is however a special case for {@const}, that can still generate an HTTP 500 if an error occurs on a {@const} used or declared before another {@const} used by the failed() snippets.

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
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

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

@svelte-docs-bot
Copy link

Copy link

changeset-bot bot commented Mar 6, 2025

⚠️ No Changeset found

Latest commit: 87b775b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

Copy link
Contributor

github-actions bot commented Mar 6, 2025

Playground

pnpm add https://pkg.pr.new/svelte@15462

@trueadm
Copy link
Contributor

trueadm commented Mar 6, 2025

I'm still unsure about this strategy, as it won't scale for the case where you don't have a failed snippet, but you do instead use the onerror to update state instead to indicate an error – which is a valid pattern that we've been promoting.

<script>
  let error = $state();
</script>

<Header error={error} />

<Form>
  <svelte:boundary onerror={e => error = e}>
    <ComponentThatErrors />
  </svelte:boundary>
</Form>

Not to mention this approach will likely break in the future if we ever plan on streaming the SSR HTML payload.

@adiguba
Copy link
Contributor Author

adiguba commented Mar 6, 2025

it won't scale for the case where you don't have a failed snippet, but you do instead use the onerror to update state instead to indicate an error

Actually this won't work with SSR, because an error will cause an HTTP 500 and the page will not be rendered at all !

With this PR in case of error the SSR will skip the svelte:boundary children with no replacement since there is no failed() snippet.
But the page will load, and the client will try to render the boundary children, and eventually run the onerror on client-side...

For streaming, I have never done this, but it may be possible to "pause" the streaming to process this. No ?

@adiguba
Copy link
Contributor Author

adiguba commented Mar 8, 2025

Hi,

I made a small example here to show the error with the current version : https://github.com/adiguba/boundary-demo
=> The three page that have error on <svelte:boundary> will show an HTTP 500.

Switch to this PR with pnpm add https://pkg.pr.new/svelte@15462 and all these page will return the content and be functionnal (with some "page shift" due to client-side code)

@trueadm
Copy link
Contributor

trueadm commented Mar 8, 2025

@adiguba For me, the fact that no other framework seems to render error boundaries on the server and yet don't suffer issues means that maybe we're missing an API on the server that is unrelated to error boundaries?

@Rich-Harris
Copy link
Member

This feels a little dangerous to me. The fact that we can't invoke onerror handlers during SSR is definitely a problem, but I'm more concerned by the fact that rendering errors would just get swallowed with this approach. For example it's impossible to deploy an update to https://svelte.dev that causes the docs to break, because those pages are prerendered; if we had a <svelte:boundary> that swallowed errors caused by type changes or broken links, it would likely take us a while to discover them. Similarly, if I'm monitoring the rate of 500s in my server logs, I'll notice if something is failing to server-render in production, but not if my server is happily rendering failed snippets instead of the intended markup.

It might not feel totally ideal to 500 the entire page if it would have been possible to serve mostly-functional HTML instead, but I'd definitely prefer it to this, I think.

@adiguba
Copy link
Contributor Author

adiguba commented Mar 13, 2025

What's about a way to capture all svelte:boundary errors during SSR/prerender ?

=> render() could return an array containing the errors that occurred during page generation.
So SvelteKit can still send an HTTP 500, but with a "mostly-functional HTML".

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