Svelte
Стриминг данных из load
Страница товара в интернет-магазине: название, цена и фото берутся из быстрого кеша за 20 миллисекунд, а блок персональных рекомендаций считается медленной моделью три секунды. Если ждать рекомендации в load, посетитель три секунды смотрит на белый экран. Стриминг разрывает эту связку: SvelteKit отдаёт HTML с товаром мгновенно, а рекомендации досылает по тому же HTTP-ответу, когда они готовы. Браузер показывает спиннер только в одном блоке, а не на всей странице.
- Карточки товаров: основные поля рендерятся сразу, а отзывы и похожие товары стримятся отдельными промисами
- Дашборды: заголовок и навигация показываются мгновенно, а тяжёлые графики дотекают по мере готовности агрегаций
- Ленты с персонализацией: общий шаблон ленты отдаётся сразу, а ранжированные под пользователя блоки стримятся
- Страницы с внешними API: данные из медленного стороннего сервиса не блокируют отрисовку остального контента
Предварительные знания
- Понимание load-функций: где они выполняются и что возвращают на страницу
- Знание промисов в JavaScript: создание, await, обработка отклонения
- Понимание того, что такое SSR и почему стриминг требует серверного рендеринга
Как промис из load превращается в поток
Обычный load ждёт все данные через await и возвращает готовый объект. Страница рендерится, когда самый медленный запрос завершён. Стриминг меняет правило: если в возвращаемом объекте оставить промис неразрешённым, SvelteKit не станет его ждать. Он отрендерит и отдаст HTML с уже доступными полями, а промис продолжит передаваться в том же HTTP-ответе и разрешится позже, когда данные придут.
Здесь product дождались через await, потому что без него страница бессмысленна. А getRecommendations вернули без await: это промис. SvelteKit отдаёт HTML с товаром немедленно и стримит рекомендации, когда модель досчитает. Различие в одном ключевом слове: await перед вызовом блокирует, его отсутствие делает поле стримящимся.
- await в load (блокирующий) — Страница ждёт самый медленный запрос. Один экран, но первый байт приходит поздно. Подходит для критичных данных, без которых страница бессмысленна
- Промис без await (стриминг) — HTML с быстрыми полями уходит сразу, медленные данные дотекают потоком. Первый экран мгновенный, но в блоке какое-то время виден каркас
Стримить промис можно только из серверного load (+page.server.js или +layout.server.js) и только при ssr=true. Универсальный load в +page.js при клиентской навигации возвращает данные обычным образом, а механизм потоковой передачи HTML относится именно к серверному рендерингу ответа.
В load из объекта возвращается поле reviews: fetchReviews() без await. Что сделает SvelteKit при серверном рендеринге?
Разворачивание стрима на странице через await
Стримящееся поле приходит на страницу как промис. Чтобы показать каркас, потом данные, а при сбое ошибку, используется блок {#await}. Он имеет три части: тело до then рендерится, пока промис в ожидании; ветка then получает разрешённое значение; ветка :catch получает причину отклонения. Это тот же блок, что и в обычном Svelte, но здесь он управляет именно потоковыми данными из load.
Поле data.product доступно сразу и рендерится без await: оно дождалось в load. А data.recommendations это промис, и блок await показывает скелетон, пока он не разрешится. Когда поток донесёт данные, Svelte заменит каркас списком. Если промис отклонится, сработает ветка :catch, и пользователь увидит сообщение об ошибке вместо повисшего спиннера.
Ветка :catch обязательна для стримящихся данных. Если её опустить, отклонение промиса не будет обработано на странице, и сбой медленного запроса даст необработанную ошибку в потоке вместо аккуратного фолбэка. Каждый стримящийся промис заслуживает свой :catch.
Пока стрим не завершён, HTTP-ответ остаётся открытым. SvelteKit вставляет недостающие данные через скрипт в конце потока, и Svelte разрешает промис на клиенте. Поэтому стриминг и требует SSR: именно открытый серверный ответ доносит поздние куски.
На странице стримящееся поле data.stats разворачивается блоком {#await data.stats}{:then s}...{/await} без ветки :catch. Запрос статистики на сервере падает с ошибкой. Что увидит пользователь?
Когда стриминг помогает, а когда вредит
Стриминг это компромисс, а не бесплатное ускорение. Он сокращает время до первого экрана, разрешая отдать HTML до готовности медленных данных. Но он же добавляет работу: каркасы, ветки ошибок, более сложный поток рендеринга. Выигрыш реален, когда на странице есть и быстрые, и заметно медленные данные. Если все запросы быстрые, стриминг только усложняет код без пользы.
| Ситуация | Стримить? | Почему |
|---|---|---|
| Заголовок быстрый, рекомендации медленные | Да | Первый экран мгновенный, медленный блок дотекает позже |
| Без этих данных страница бессмысленна | Нет | Каркас без сути это пустой экран, лучше await в load |
| Все запросы укладываются в 50 мс | Нет | Выигрыша нет, только лишние каркасы и ветки ошибок |
| Данные нужны для SEO и индексации | Нет | Стримящийся блок может не попасть в HTML, который видит краулер |
Критичные для SEO данные не стоит стримить. Поисковый робот часто читает первичный HTML, а стримящийся блок дотекает после него и может не попасть в проиндексированную версию. Заголовок, описание и основной контент, на который опирается выдача, нужно await-ить в load. Стриминг оставляют для вторичных блоков: рекомендаций, отзывов, аналитики, где задержка индексации не критична.
Практическое правило: один await в load для данных, без которых первый экран не имеет смысла, и промисы без await для всего, что медленно и второстепенно. Так первый байт приходит быстро, а пользователь не ждёт самый тормозной запрос ради остального контента.
Страница статьи блога: текст статьи нужен для SEO, а блок комментариев грузится из медленного стороннего сервиса. Как разумнее построить load?
Связь с другими темами
Стриминг надстраивается над load и серверным рендерингом, а на клиенте перекликается с асинхронным состоянием:
- Load-функции — Стриминг это возврат промисов из того же load, который раньше возвращал готовые данные
- Опции страницы — Стриминг включается только при ssr=true, что управляется опцией ssr
- Асинхронные данные и реактивность — Тот же приём раннего каркаса и поздних данных применяется и на чистом клиенте
Итог
- Стриминг достигается возвратом из load незавершённого промиса вместо await: SvelteKit отдаёт HTML сразу, а данные досылает потоком
- Критичные данные нужно await-ить в load, а некритичные и медленные возвращать промисом, чтобы они не блокировали первый экран
- На странице промис разворачивается блоком {#await ... then ... :catch}, который показывает каркас, затем данные или ошибку
- Стриминг работает только при ssr=true, потому что опирается на постепенную передачу серверного HTML-ответа
- Отклонённый стримящийся промис нужно обрабатывать в :catch, иначе ошибка проявится только в потоке, а не в load
Связанные уроки
- sv-19-load-functions — Стриминг это особый способ возврата данных из load, поэтому базовое понимание load-функций обязательно
- sv-28-page-options — Стриминг работает только при ssr=true, режим которого задаётся опциями страницы из предыдущего урока
- sv-34-async-data — И стриминг, и асинхронные данные на клиенте решают одну задачу: показать каркас сразу, а данные позже