3.9 KiB
| title |
|---|
| Hydratable data |
In Svelte, when you want to render asynchronous content data on the server, you can simply await it. This is great! However, it comes with a pitfall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:
<script>
import { getUser } from 'my-database-library';
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
</script>
<h1>{user.name}</h1>
That's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. hydratable is a low-level API built to solve this problem. You probably won't need this very often — it will be used behind the scenes by whatever datafetching library you use. For example, it powers remote functions in SvelteKit.
To fix the example above:
<script>
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>
This API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration:
import { hydratable } from 'svelte';
const rand = hydratable('random', () => Math.random());
If you're a library author, be sure to prefix the keys of your hydratable values with the name of your library so that your keys don't conflict with other libraries.
Serialization
All data returned from a hydratable function must be serializable. But this doesn't mean you're limited to JSON — Svelte uses devalue, which can serialize all sorts of things including Map, Set, URL, and BigInt. Check the documentation page for a full list. In addition to these, thanks to some Svelte magic, you can also fearlessly use promises:
<script>
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2)
}
});
</script>
{await promises.one}
{await promises.two}
CSP
hydratable adds an inline <script> block to the head returned from render. If you're using Content Security Policy (CSP), this script will likely fail to run. You can provide a nonce to render:
// @errors: 2304 2708
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
csp: { nonce }
});
This will add the nonce to the script block, on the assumption that you will later add the same nonce to the CSP header of the document that contains it:
response.headers.set(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
);
It's essential that a nonce — which, British slang definition aside, means 'number used once' — is only used when dynamically server rendering an individual response.
If instead you are generating static HTML ahead of time, you must use hashes instead:
// @errors: 2304 2708
const { head, body, hashes } = await render(App, { csp: { hash: true } });
hashes.script will be an array of strings like ["sha256-abcd123"]. We recommend using nonce over hash if you can, as hash will interfere with streaming SSR in the future.