Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions apps/svelte.dev/content/docs/svelte/06-runtime/05-hydratable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
NOTE: do not edit this file, it is generated in apps/svelte.dev/scripts/sync-docs/index.ts
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:

```svelte
<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](/docs/kit/remote-functions).

To fix the example above:

```svelte
<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:

```ts
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`](https://npmjs.com/package/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:

```svelte
<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}
```
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,16 @@ $effect(() => {

Often when encountering this issue, the value in question shouldn't be state (for example, if you are pushing to a `logs` array in an effect, make `logs` a normal array rather than `$state([])`). In the rare cases where you really _do_ need to write to state in an effect — [which you should avoid]($effect#When-not-to-use-$effect) — you can read the state with [untrack](svelte#untrack) to avoid adding it as a dependency.

### experimental_async_fork
### flush_sync_in_effect

```
Cannot use `fork(...)` unless the `experimental.async` compiler option is `true`
Cannot use `flushSync` inside an effect
```

The `flushSync()` function can be used to flush any pending effects synchronously. It cannot be used if effects are currently being flushed — in other words, you can call it after a state change but _not_ inside an effect.

This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.

### fork_discarded

```
Expand All @@ -154,6 +158,25 @@ Cannot create a fork inside an effect or when state changes are pending
`getAbortSignal()` can only be called inside an effect or derived
```

### hydratable_missing_but_required

```
Expected to find a hydratable with key `%key%` during hydration, but did not.
```

This can happen if you render a hydratable on the client that was not rendered on the server, and means that it was forced to fall back to running its function blockingly during hydration. This is bad for performance, as it blocks hydration until the asynchronous work completes.

```svelte
<script>
import { hydratable } from 'svelte';

if (BROWSER) {
// bad! nothing can become interactive until this asynchronous work is done
await hydratable('foo', get_slow_random_number);
}
</script>
```

### hydration_failed

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,25 @@ The easiest way to log a value as it changes over time is to use the [`$inspect`
%handler% should be a function. Did you mean to %suggestion%?
```

### hydratable_missing_but_expected

```
Expected to find a hydratable with key `%key%` during hydration, but did not.
```

This can happen if you render a hydratable on the client that was not rendered on the server, and means that it was forced to fall back to running its function blockingly during hydration. This is bad for performance, as it blocks hydration until the asynchronous work completes.

```svelte
<script>
import { hydratable } from 'svelte';

if (BROWSER) {
// bad! nothing can become interactive until this asynchronous work is done
await hydratable('foo', get_slow_random_number);
}
</script>
```

### hydration_attribute_changed

```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<!-- This file is generated by scripts/process-messages/index.js. Do not edit! -->

### async_local_storage_unavailable

```
The node API `AsyncLocalStorage` is not available, but is required to use async server rendering.
```

Some platforms require configuration flags to enable this API. Consult your platform's documentation.

### await_invalid

```
Expand All @@ -14,10 +22,51 @@ You (or the framework you're using) called [`render(...)`](svelte-server#render)
The `html` property of server render results has been deprecated. Use `body` instead.
```

### hydratable_clobbering

```
Attempted to set `hydratable` with key `%key%` twice with different values.

%stack%
```

This error occurs when using `hydratable` multiple times with the same key. To avoid this, you can:
- Ensure all invocations with the same key result in the same value
- Update the keys to make both instances unique

```svelte
<script>
import { hydratable } from 'svelte';

// which one should "win" and be serialized in the rendered response?
const one = hydratable('not-unique', () => 1);
const two = hydratable('not-unique', () => 2);
</script>
```

### hydratable_serialization_failed

```
Failed to serialize `hydratable` data for key `%key%`.

`hydratable` can serialize anything [`uneval` from `devalue`](https://npmjs.com/package/uneval) can, plus Promises.

Cause:
%stack%
```

### lifecycle_function_unavailable

```
`%name%(...)` is not available on the server
```

Certain methods such as `mount` cannot be invoked while running in a server context. Avoid calling them eagerly, i.e. not during render.

### server_context_required

```
Could not resolve `render` context.
```

Certain functions such as `hydratable` cannot be invoked outside of a `render(...)` call, such as at the top level of a module.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!-- This file is generated by scripts/process-messages/index.js. Do not edit! -->

### unresolved_hydratable

```
A `hydratable` value with key `%key%` was created, but at least part of it was not used during the render.

The `hydratable` was initialized in:
%stack%
```

The most likely cause of this is creating a `hydratable` in the `script` block of your component and then `await`ing
the result inside a `svelte:boundary` with a `pending` snippet:

```svelte
<script>
import { hydratable } from 'svelte';
import { getUser } from '$lib/get-user.js';

const user = hydratable('user', getUser);
</script>

<svelte:boundary>
<h1>{(await user).name}</h1>

{#snippet pending()}
<div>Loading...</div>
{/snippet}
</svelte:boundary>
```

Consider inlining the `hydratable` call inside the boundary so that it's not called on the server.

Note that this can also happen when a `hydratable` contains multiple promises and some but not all of them have been used.
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<!-- This file is generated by scripts/process-messages/index.js. Do not edit! -->

### experimental_async_required

```
Cannot use `%name%(...)` unless the `experimental.async` compiler option is `true`
```

### invalid_default_snippet

```
Expand Down
13 changes: 13 additions & 0 deletions apps/svelte.dev/content/docs/svelte/98-reference/20-svelte.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getAllContexts,
getContext,
hasContext,
hydratable,
hydrate,
mount,
onDestroy,
Expand Down Expand Up @@ -433,6 +434,18 @@ function hasContext(key: any): boolean;



## hydratable

<div class="ts-block">

```dts
function hydratable<T>(key: string, fn: () => T): T;
```

</div>



## hydrate

Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
Expand Down
Loading