20. Streaming SSR
Streaming SSR позволяет браузеру получать и рендерить HTML постепенно, не дожидаясь полной генерации страницы на сервере. Remix поддерживает стриминг через defer() и React Streaming.
Как работает Streaming
Заголовок раздела «Как работает Streaming»Без стриминга: Сервер → [ждёт 3000ms] → [полный HTML] → Браузер показывает страницу
Со стримингом: Сервер → [100ms] → [начало HTML] → Браузер начинает рендер → [500ms] → [секция 1] → Браузер обновляет → [3000ms]→ [секция 2] → Браузер обновляетentry.server.tsx для Streaming
Заголовок раздела «entry.server.tsx для Streaming»import { renderToPipeableStream } from "react-dom/server";import { RemixServer } from "@remix-run/react";import { isbot } from "isbot";
const ABORT_DELAY = 5_000;
export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext,) { const userAgent = request.headers.get("user-agent"); const callbackName = isbot(userAgent ?? "") ? "onAllReady" // Боты ждут полного HTML : "onShellReady"; // Пользователи — стриминг
return new Promise((resolve, reject) => { let didError = false; let shellRendered = false;
const { pipe, abort } = renderToPipeableStream( <RemixServer context={remixContext} url={request.url} />, { [callbackName]() { shellRendered = true; const body = new PassThrough();
responseHeaders.set("Content-Type", "text/html");
resolve( new Response(body, { headers: responseHeaders, status: didError ? 500 : responseStatusCode, }) );
pipe(body); }, onShellError(error) { reject(error); }, onError(error) { didError = true; if (shellRendered) console.error(error); }, } );
setTimeout(abort, ABORT_DELAY); });}Паттерн: критические данные + медленные
Заголовок раздела «Паттерн: критические данные + медленные»export async function loader({ request }) { // Параллельно запускаем запросы const [user, meta] = await Promise.all([ getUser(request), // ~50ms — блокируем getPageMeta(request), // ~100ms — блокируем ]);
// Медленные запросы — стримим return defer({ user, meta, products: getProducts(), // ~1000ms — стриминг reviews: getReviews(), // ~1500ms — стриминг recommendations: getRecs(), // ~2000ms — стриминг });}SEO и стриминг
Заголовок раздела «SEO и стриминг»Для поисковых ботов стриминг отключается (они получают полный HTML):
const callbackName = isbot(userAgent) ? "onAllReady" // Ждём полной генерации для ботов : "onShellReady"; // Стриминг для пользователейTimeout для медленных данных
Заголовок раздела «Timeout для медленных данных»// Если данные не пришли за 3 секунды — показываем fallbackconst slowPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 3000));
return defer({ data: Promise.race([getSlowData(), slowPromise]),});