Svelte
Паттерны загрузки данных
Страница заказа тянет профиль пользователя, список заказов и рекомендации. Написанная в лоб, она делает три запроса один за другим, и пользователь ждёт сумму всех трёх задержек. Хуже того, рекомендации считаются на отдельном медленном сервисе и держат весь экран ради второстепенного блока. SvelteKit даёт инструменты на оба случая: независимые запросы идут параллельно, а медленные можно отдать стримингом - страница появляется сразу, а тяжёлый блок дорисовывается, когда будет готов. Плюс точечная инвалидация перезагружает ровно те данные, что устарели.
- Страница заказа: профиль, заказы и рекомендации грузятся параллельно вместо суммы трёх задержек
- Дашборд: быстрые метрики рендерятся сразу, тяжёлый отчёт стримится и появляется через секунду
- Лента с фильтром: invalidate перезагружает только список по новому фильтру, не трогая шапку и сайдбар
- После логина: invalidateAll обновляет все load, чтобы зависящие от пользователя блоки показали его данные
- Layout + page: общий load в +layout берёт пользователя один раз, страница не запрашивает его повторно
Предварительные знания
- Load-функции SvelteKit: +page.js, +page.server.js, проп data
- Промисы, Promise.all и async/await в JavaScript
- Блок {#await} в Svelte для отображения промиса в разметке
Водопады и параллельная загрузка
Водопад - это цепочка запросов, где каждый ждёт завершения предыдущего без необходимости. Если профиль, заказы и рекомендации не зависят друг от друга, но написаны как три последовательных await, общее время равно их сумме. Решение то же, что в базовом load: запускать независимые промисы сразу и дожидаться их вместе через Promise.all.
Есть и второй источник водопада - между уровнями маршрута. load в +layout.server.js и load в +page.server.js по умолчанию исполняются параллельно, а не друг за другом. Поэтому общие для раздела данные (пользователь, навигация) логично грузить один раз в layout, а страница берёт своё. Если же странице действительно нужен результат layout, его получают через await parent(), и только тогда возникает осознанная последовательность.
| Ситуация | Поведение | Когда уместно |
|---|---|---|
| Последовательные await | Водопад, времена складываются | Только если второй запрос зависит от первого |
| Promise.all | Параллель, время = самый медленный | Независимые запросы внутри одного load |
| layout load + page load | Идут параллельно по умолчанию | Общие данные в layout, частные на странице |
| await parent() | Page ждёт данные layout | Когда странице реально нужен результат layout |
Вызов await parent() без необходимости вновь создаёт водопад: страница начнёт грузить своё только после layout. Его применяют, лишь когда запрос страницы действительно зависит от данных родителя, а не по привычке.
load загружает три независимых ресурса тремя последовательными await. Как ускорить без потери данных?
depends, invalidate и invalidateAll
SvelteKit перезапускает load автоматически, когда меняются его наблюдаемые зависимости. Если load обратился к params.id, url.searchParams или fetch на некий URL, фреймворк запоминает это и перезапустит load при изменении соответствующей сущности. Но иногда нужно перезагрузить данные по событию, не связанному напрямую с url, например после мутации. Для этого есть управляемая инвалидация.
depends(id) регистрирует произвольный идентификатор как зависимость данного load. Вызов invalidate(id) перезапускает ровно те load, что объявили этот идентификатор, не трогая остальные - шапка и сайдбар не дёргаются. invalidate можно передать и URL: invalidate('/api/orders') перезапустит каждый load, который fetch-ил этот адрес. invalidateAll() перезапускает все активные load сразу.
| Вызов | Что перезапускает | Типичный повод |
|---|---|---|
| invalidate('app:orders') | load с depends('app:orders') | После создания или отмены заказа |
| invalidate('/api/orders') | load, дергавшие этот URL | Когда зависимость завязана на конкретный fetch |
| invalidateAll() | Все активные load | Смена пользователя, логин или логаут |
Собственные идентификаторы зависимостей удобно именовать с префиксом, например app:orders или app:cart. Это отделяет их от URL-зависимостей и делает явным, что именно инвалидируется при вызове invalidate.
После создания заказа нужно обновить только список заказов, не перезагружая шапку и сайдбар. Как это сделать?
Стриминг медленных данных
Иногда часть данных критична для первого экрана, а часть - нет. Профиль и заказы нужны сразу, а рекомендации с медленного сервиса можно дорисовать позже. Если await-ить всё в load, страница будет ждать самый медленный запрос. SvelteKit умеет стримить: критичные данные await-ятся и попадают в HTML сразу, а медленный промис возвращается из load неразрешённым.
Ключевой приём: промис recommendations не await-ят в load, а возвращают как есть. SvelteKit отдаёт первый HTML с уже готовым user, а значение промиса досылает по тому же соединению, когда оно разрешится. В разметке блок {#await} показывает заглушку, затем результат или ошибку. Первый полезный экран появляется без ожидания медленного сервиса.
Стриминг требует платформы с поддержкой потоковых ответов. На статическом хостинге (полностью пререндеренном) его нет - там данные должны быть готовы на этапе сборки. Кроме того, у неразрешённого промиса важно обработать ошибку (ветка {:catch} или .catch), иначе непойманный reject обвалит ответ.
Профиль нужен сразу, а рекомендации считает медленный сервис. Как показать страницу быстро, не дожидаясь рекомендаций?
Связь с другими темами
Этот урок развивает базовый load в сторону скорости и управляемости:
- Load-функции — Фундамент: где живёт load и как данные попадают на страницу. Здесь добавляется производительность
- Гидрация — Стриминг и точечная гидрация служат одной цели - ранний полезный экран и быстрые Core Web Vitals
- Hooks — locals и сессия определяют, какие load зависят от пользователя и что инвалидировать после входа
Итог
- Водопад возникает, когда независимые запросы идут последовательными await; параллельный запуск через Promise.all устраняет его
- Load в +layout исполняется отдельно от load страницы и параллельно ему; общие данные грузят один раз в layout
- depends(id) объявляет произвольную зависимость load; invalidate(id) перезапускает именно те load, что от неё зависят
- invalidateAll() перезапускает все активные load - удобно после смены пользователя; зависимость от url создаётся автоматически
- Не дожидаясь промиса в load, его возвращают как есть; {#await} в разметке стримит результат, не блокируя первый рендер
Связанные уроки
- sv-19-load-functions — Базовый load уже знаком; здесь те же функции выводятся на уровень производительности и управляемого обновления
- sv-26-hydration — Стриминг промисов и точечная гидрация - две стороны идеи отдавать пользователю готовое как можно раньше
- sv-22-hooks — Зависимости load часто завязаны на сессию из locals; инвалидация после логина обновляет данные, зависящие от пользователя