31. Streaming SSR
Streaming SSR — это техника серверного рендеринга, при которой HTML отправляется клиенту частями по мере готовности, а не целиком после того, как все данные загружены. Solid.js поддерживает потоковую передачу из коробки через renderToStream.
Проблема классического SSR
Заголовок раздела «Проблема классического SSR»В классическом SSR (renderToString) клиент ждёт, пока сервер:
- Получит запрос
- Загрузит все данные для страницы (API, БД)
- Сгенерирует полный HTML
- Отправит ответ
Если один из API-запросов медленный — весь пользователь ждёт:
Клиент запрашивает страницу │ ▼Сервер: загружает данные (500мс)............... │ ▼Сервер: рендерит HTML ─────────────────────────→ Клиент получает HTML (500мс задержки)Streaming SSR: постепенная отдача
Заголовок раздела «Streaming SSR: постепенная отдача»При потоковом SSR клиент сразу получает “скелет” страницы, а контент подгружается по мере готовности:
Клиент запрашивает страницу │ ▼ 0мсСервер: → <html><head>...</head><body>... (отправляет сразу!) │ 50мс ▼Сервер: → <header>...</header><nav>... (готово, отправляем) │ 200мс ▼Сервер: → <article>...</article> (загружен из БД, отправляем) │ 500мс ▼Сервер: → <aside>рекомендации...</aside></body></html> (последний блок)renderToStream vs renderToString
Заголовок раздела «renderToStream vs renderToString»import { renderToString, renderToStream } from 'solid-js/web';
// Классический SSR — ждёт всех данныхconst html = await renderToString(() => <App />);res.send(html);
// Streaming SSR — отдаёт HTML по мере готовностиconst stream = renderToStream(() => <App />);// stream — это Node.js ReadableStreamstream.pipe(res);
// С заголовками (чтобы браузер не буферизовал)res.setHeader('Content-Type', 'text/html');res.setHeader('Transfer-Encoding', 'chunked');renderToStream(() => <App />).pipe(res);Suspense + Streaming = магия
Заголовок раздела «Suspense + Streaming = магия»Ключевая концепция: <Suspense> определяет границы потока. Каждый Suspense отдаётся независимо:
function Page() { return ( <html> <body> {/* Этот блок придёт сразу — нет async данных */} <Header /> <nav>...</nav>
{/* Этот блок придёт, когда загрузится статья (200мс) */} <Suspense fallback={<ArticleSkeleton />}> <Article /> </Suspense>
{/* Этот блок придёт последним (500мс) */} <Suspense fallback={<SidebarSkeleton />}> <Sidebar /> </Suspense> </body> </html> );}При потоковом SSR браузер:
- Получает статичный HTML немедленно, показывает скелетон
- Получает chunk с
<Article>через 200мс, заменяет скелетон - Получает chunk с
<Sidebar>через 500мс, заменяет скелетон
Lazy-компоненты в потоке
Заголовок раздела «Lazy-компоненты в потоке»import { lazy } from 'solid-js';import { Suspense } from 'solid-js';
// Lazy-компонент разделяет код (code splitting) и интегрируется со Streamingconst HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() { return ( <div> <QuickStats /> {/* Приходит первым */}
<Suspense fallback={<div>Загрузка графика...</div>}> <HeavyChart /> {/* Приходит когда JS-бандл загружен */} </Suspense> </div> );}Прогрессивная гидратация
Заголовок раздела «Прогрессивная гидратация»Solid.js поддерживает out-of-order streaming — Suspense-блоки могут разрешаться в любом порядке, не только последовательно:
// Сервер отправляет блоки в порядке готовности:// 1. Сначала <Header> (0мс)// 2. Потом <Sidebar> (100мс) — хотя стоит ПОСЛЕ <Article> в JSX!// 3. Потом <Article> (300мс)function Page() { return ( <> <Header /> <Suspense fallback={<ArticleSkeleton />}> <Article /> {/* 300мс */} </Suspense> <Suspense fallback={<SidebarSkeleton />}> <Sidebar /> {/* 100мс — придёт первым из двух! */} </Suspense> </> );}Под капотом Solid вставляет <script> теги, которые манипулируют DOM для замены скелетонов в правильные позиции.
Конфигурация в SolidStart
Заголовок раздела «Конфигурация в SolidStart»import { defineConfig } from '@solidjs/start/config';
export default defineConfig({ server: { // Включаем streaming experimental: { asyncContext: true, } }});
// src/routes/index.tsx — streaming работает автоматически с Suspenseexport default function Home() { return ( <main> <h1>Мой сайт</h1> <Suspense fallback={<p>Загрузка постов...</p>}> <PostList /> </Suspense> </main> );}Метрики производительности
Заголовок раздела «Метрики производительности»| Метрика | renderToString | renderToStream |
|---|---|---|
| TTFB (Time to First Byte) | После загрузки всех данных | Немедленно |
| FCP (First Contentful Paint) | После всего HTML | После первого chunk |
| LCP (Largest Contentful Paint) | После всего HTML | Зависит от расположения |
| TTI (Time to Interactive) | После гидратации | Progressive |
| Сложность | Проще | Требует настройки |
Интерактивный пример
Заголовок раздела «Интерактивный пример»Симуляция потокового SSR — наблюдай, как HTML-чанки прибывают один за другим: