Перейти к содержимому

Данные с поддержкой гидратации

В Svelte, когда нужно отрендерить на сервере данные, полученные асинхронно, достаточно просто сделать await. Это здорово! Однако есть один подводный камень: при гидратации на клиенте Svelte вынужден заново выполнить эту асинхронную работу, и весь процесс гидратации блокируется ровно на то время, которое требуется для её завершения:

<script>
import { getUser } from 'my-database-library';
// Это получит пользователя на сервере, отрендерит имя пользователя в <h1>,
// а затем во время гидратации на клиенте снова получит пользователя,
// блокируя гидратацию до тех пор, пока запрос не завершится.
const user = await getUser();
</script>
<h1>{user.name}</h1>

Это, конечно, глупо. Если мы уже проделали всю тяжёлую работу по получению данных на сервере, нет никакого смысла делать это заново во время гидратации на клиенте. hydratable — это низкоуровневый API, созданный именно для решения этой проблемы. Скорее всего, вам редко придётся использовать его напрямую: он будет применяться «под капотом» той библиотекой для загрузки данных, которую вы выберете. Например, именно он лежит в основе удалённых функций в SvelteKit.

Чтобы исправить пример выше:

<script>
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
// Во время серверного рендеринга результат `getUser` будет сериализован и сохранён,
// привязан к указанному ключу и встроен в содержимое `<head>`.
// При гидратации вместо повторного вызова `getUser` будет использована уже сериализованная версия.
// После завершения гидратации при повторных вызовах будет снова выполняться `getUser`.
const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>

Этот API также можно использовать для получения случайных или зависящих от времени значений, которые остаются неизменными между серверным рендерингом и гидратацией. Например, чтобы получить случайное число, которое не будет меняться при гидратации:

import { hydratable } from 'svelte';
const rand = hydratable('random', () => Math.random());

Если вы автор библиотеки, обязательно добавляйте префикс с именем вашей библиотеки к ключам значений hydratable, чтобы избежать конфликтов с другими библиотеками.

Все данные, возвращаемые из функции hydratable, должны быть сериализуемыми. Но это вовсе не означает, что вы ограничены только JSON — Svelte использует библиотеку devalue, которая поддерживает сериализацию множества типов, включая Map, Set, URL и BigInt. Полный список смотрите в её документации.

Более того, благодаря магии 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}

hydratable добавляет встроенный блок <script> в head, возвращаемый из render. Если вы используете Политику безопасности контента (CSP), этот скрипт, скорее всего, не выполнится. Вы можете передать nonce в render.

server.js
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
csp: { nonce }
});

Это добавит nonce к блоку скрипта, предполагая, что вы позже добавите тот же самый nonce в CSP-заголовок документа, который его содержит:

server.js
response.headers.set(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
);

Важно, чтобы nonce — что, если отвлечься от британского сленгового значения, означает «число, используемое один раз» — использовался только при динамическом серверном рендеринге индивидуального ответа.

Если же вы генерируете статический HTML заранее, вы должны использовать хэши:

server.js
const { head, body, hashes } = await render(App, {
csp: { hash: true }
});

hashes.script будет массивом строк, например ["sha256-abcd123"]. Как и с nonce, хэши должны использоваться в вашем CSP-заголовке:

server.js
response.headers.set(
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`
);

Мы рекомендуем использовать nonce вместо хэша, если это возможно, так как hash будет мешать потоковому SSR в будущем.