Async SSR #16784
Replies: 7 comments 13 replies
-
What happens if a promise awaited outside a boundary results in an error only on the client? |
Beta Was this translation helpful? Give feedback.
-
What's the plan for interoperability with the already existing The way I see it: there isn't any reason (syntactically) why import { browser } from '$app/environment';
export async function work() {
const value = Math.random();
console.log({ browser, value });
return value;
} {#await work()}
Pending...
{:then output}
{output}
{/await} <svelte:boundary>
{await work()}
{#snippet pending()}
Pending...
{/snippet}
</svelte:boundary> Note In both cases, However, in reality, we will see some mismatch in behavior (and thus a breaking change!). In the former example, the For our contrived example of returning More generally, what will be the fate of the |
Beta Was this translation helpful? Give feedback.
-
One thing you can do today with load functions is server side rendering the page on initial page load (awaiting a promise in the load function) But when doing client side navigation streaming the promise with a loading state. Can this pattern be achieved in this new paradigm? We don't really use it anywhere so it may not matter. I was just curious because I couldn't think how it would work? A conditional pending snippet? |
Beta Was this translation helpful? Give feedback.
-
How would this all work with |
Beta Was this translation helpful? Give feedback.
-
As someone who converted from years of Next.js-based development, a significant feature that I have been missing from the dark side is
Given that we will be able to have both SSR and streaming of async data for regular page render, how possible are these two features above? |
Beta Was this translation helpful? Give feedback.
-
In the Supabase docs for SvelteKit w/ SSR they show how to initialize a client in import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr'
import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import type { LayoutLoad } from './$types'
export const load: LayoutLoad = async ({ data, depends, fetch }) => {
depends('supabase:auth')
const supabase = isBrowser()
? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY, {
global: {
fetch,
},
})
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY, {
global: {
fetch,
},
cookies: {
getAll() {
return data.cookies
},
},
})
const { data: { session } } = await supabase.auth.getSession()
const { data: { user } } = await supabase.auth.getUser()
return { session, supabase, user }
} And then load it into <script>
import { invalidate } from '$app/navigation'
import { onMount } from 'svelte'
let { data, children } = $props()
let { session, supabase } = $derived(data)
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((_, newSession) => {
if (newSession?.expires_at !== session?.expires_at) {
invalidate('supabase:auth')
}
})
return () => data.subscription.unsubscribe()
})
</script>
{@render children()} This also has the effect of injecting a Supabase client to your pages, which is conditionally browser or server variant depending on where the environment where the code is run. How would I do this with remote functions? |
Beta Was this translation helpful? Give feedback.
-
Could you elaborate a bit on this? I'm trying to understand how you would implement this yourself. From what I gather, in sveltekit, the data cache is embedded in the initial html and then the client side code of remote functions acts as if the cache is already populated?. Thanks :) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
The Svelte team has been hard at work on asynchronous rendering, with client-side support and remote functions as our first two efforts. It’s time for the third: SSR.
Background
There are three core pieces to the async problem:
The interplay between these creates a number of challenges for anyone seeking to create a seamless end-to-end experience.
Design
When asynchronously rendering on the server, any
await
expressions not contained in asvelte:boundary
with apending
snippet will block until their associated promise resolves. Await expressions behind asvelte:boundary
with apending
snippet will not be reached — but more on that later. This means that if you write the following:The resulting HTML output will look like this:
…and if you had a boundary with a
pending
snippet:…the output would look like this:
Because we have cool compiler superpowers, we can also optimize your code. The following would take one second to render, not two:
You can use
await
everywhere — in component<script>
blocks, in$derived
expressions, in templates, and in normal modules and async functions.What about hydration?
When you render asynchronous content on the server, then send it to the client and hydrate it, one of two things will happen:
pending
snippet on the server, the client will kick off the asynchronous work, replacing thepending
snippet when the asynchronous work is donepending
boundary), that content will be “normally” hydratedThe latter behavior might raise some eyebrows. What it means, practically, is that this:
Would output something like this on the server:
…and then, on the client during hydration, we’d wait for another random amount of time and you’d see it change to:
This really isn’t very desirable behavior. During hydration, you typically want to stick with the stuff you rendered on the server unless it actually needs to change! What we need to be able to do is render something asynchronously on the server, then hydrate it synchronously on the client based on what we rendered on the server. This requires serialization at some point. Somehow, we have to get the data you’ve rendered over the wire in such a way that we can retrieve it on the client and hydrate it into reactive JavaScript.
React solves this problem by making the serialization boundary be the client component. Data passed from a server component to a client component has to be serializable so that it can be sent over the wire and used in hydration. The downside of this approach is that React has to live in two worlds, where some of your components are server components (and therefore only run on the server) and some of them are client components (and therefore run on both the server, for initial renders, and the client for subsequent client-side ones). Svelte has always been a very isomorphic framework — we believe the Svelte code you write should be able to run equally well on the server and the client. Keeping to these roots, we think the more logical place to put this serialization boundary is at the resource level — at the actual place you’re fetching the data.
Thankfully, we’ve already taken care of this problem for you with remote functions in SvelteKit. Given a setup like this:
…when server rendering, the result of
getUser
will be inlined into the resulting HTML, and during hydration,getUser
will simply return the data synchronously — no delays or additional requests involved.“But Elliott, I don’t want to use SvelteKit!” That’s fine! Remote functions have been our exploration into what the best possible version of data-fetching in SvelteKit could be. Now that we’ve had that exploration, we’re working on bringing some of that goodness into Svelte core, so that other framework authors can build similar solutions, or you can make your own.
But what about
pending
snippets?The above behavior could be kind of annoying! It means that you have to send your server-rendered content to the browser, then kick off whatever asynchronous work needs to happen to render the non-pending contents of your boundary. We plan to fix this with streaming. In the future, this:
will immediately render the
pending
snippet, but start rendering the non-pending content in the background. This non-pending content can then be sent to the browser after the initial response. This is the best of both worlds, where you can de-prioritize nonessential content by rendering a skeleton without needing multiple server round trips to fully hydrate your application.Okay, how do I try it?
It’s out in the latest version of
svelte
! Just setexperimental.async: true
in your Svelte compile options. Once you’ve done this,await
ing or calling.then
on the result ofrender
will render your component asynchronously. If you’re using a framework, that framework will have to implement support. In the very near future, we’ll release an update to SvelteKit to pick up yourcompileOptions
and automatically use async server rendering if you’ve turned the experimental compile option on.Beta Was this translation helpful? Give feedback.
All reactions