8. Server-Side Rendering (SSR)
⚡ Server-Side Rendering (SSR) в Next.js
Заголовок раздела «⚡ Server-Side Rendering (SSR) в Next.js»SSR — это когда HTML генерируется на сервере при каждом запросе. Пользователь получает свежий, полностью заполненный HTML прямо с сервера. Не пустую страницу, не устаревший кеш — именно свежий контент для этого конкретного запроса! 🌐
Запрос пользователя │ ▼ Next.js Server │ ├─► cookies() / headers() ─── данные запроса ├─► searchParams ─── параметры URL ├─► DB запрос ─── свежие данные │ ▼ Рендеринг HTML │ ▼ Ответ клиенту (готовый HTML с данными!)🔄 Динамический рендеринг: Как включается SSR
Заголовок раздела «🔄 Динамический рендеринг: Как включается SSR»В Next.js App Router страница становится динамической (SSR) когда:
// 1. Используется cookies() — каждый запрос имеет свои cookiesimport { cookies } from 'next/headers';
export default async function ProfilePage() { const cookieStore = await cookies(); // ← Делает страницу динамической! const sessionToken = cookieStore.get('session');
const user = await getUserBySession(sessionToken?.value);
return <UserProfile user={user} />;}
// 2. Используется headers()import { headers } from 'next/headers';
export default async function LocalizedPage() { const headersList = await headers(); // ← Динамическая! const locale = headersList.get('accept-language') ?? 'ru';
const content = await getContentByLocale(locale); return <Page content={content} />;}
// 3. Используется searchParams (параметры URL ?q=...)interface Props { searchParams: Promise<{ q?: string; page?: string }>;}
export default async function SearchPage({ searchParams }: Props) { const { q = '', page = '1' } = await searchParams; // ← Динамическая!
const results = await searchPosts(q, parseInt(page));
return ( <div> <SearchInput defaultValue={q} /> <ResultsList results={results} /> <Pagination currentPage={parseInt(page)} /> </div> );}
// 4. Явная настройка force-dynamicexport const dynamic = 'force-dynamic'; // ← Явно динамическая!export const revalidate = 0; // Альтернатива: revalidate = 0
export default async function AlwaysFreshPage() { const data = await getLatestData(); return <DataDisplay data={data} />;}
// 5. noStore() — отключить кеш для конкретного запросаimport { unstable_noStore as noStore } from 'next/cache';
export default async function RealtimePage() { noStore(); // ← Делает текущий рендер динамическим
const price = await getCurrentPrice(); return <PriceDisplay price={price} />;}🌊 Streaming HTML: Не жди, стримь!
Заголовок раздела «🌊 Streaming HTML: Не жди, стримь!»Streaming — это отправка HTML по частям, пока медленные компоненты ещё загружаются:
import { Suspense } from 'react';
export default function DashboardPage() { return ( <div> {/* Мгновенно: статический контент */} <h1>Дашборд</h1> <QuickStats /> {/* Быстро — нет Suspense */}
{/* Streaming: отправляется когда готово */} <Suspense fallback={<RevenueChartSkeleton />}> <RevenueChart /> {/* Медленный запрос */} </Suspense>
<Suspense fallback={<TransactionsSkeleton />}> <TransactionsTable /> {/* Ещё один медленный */} </Suspense> </div> );}
// Медленный компонент — не блокирует остальное!async function RevenueChart() { await new Promise(r => setTimeout(r, 2000)); // Симуляция медленного API const revenue = await getMonthlyRevenue(); return <Chart data={revenue} />;}
async function TransactionsTable() { const transactions = await getRecentTransactions(); return <Table data={transactions} />;}Без Streaming:────────────────────────────────── 3s| ждём всё... |→ Страница появляется целиком
С Streaming:|====| 0.3s → Базовый HTML + скелетоны |======| 1s → RevenueChart встраивается |========| 2s → TransactionsTable встраивается🔮 Как сервер отдаёт Streaming
Заголовок раздела «🔮 Как сервер отдаёт Streaming»HTTP Response:──────────────────────────────────────Content-Type: text/htmlTransfer-Encoding: chunked
<-- Chunk 1 (мгновенно): --><!DOCTYPE html><html><head>...</head><body> <h1>Дашборд</h1> <div id="__NEXT_SUSPENSE__0"> <!-- skeleton placeholder --> <div class="skeleton">Loading chart...</div> </div>
<-- Chunk 2 (через 1s, когда RevenueChart готов): --> <script> // Вставляем готовый HTML в placeholder $RC("__NEXT_SUSPENSE__0", `<div class="chart">...</div>`) </script>
<-- Chunk 3 (через 2s): --> <script>$RC("__NEXT_SUSPENSE__1", `<table>...</table>`)</script></body></html>──────────────────────────────────────⚙️ Конфигурация динамического рендеринга
Заголовок раздела «⚙️ Конфигурация динамического рендеринга»// Варианты export const dynamic:export const dynamic = 'auto'; // По умолчанию — Next.js решаетexport const dynamic = 'force-dynamic'; // Всегда SSRexport const dynamic = 'error'; // Ошибка если нужен динамический рендерexport const dynamic = 'force-static'; // Всегда статика (игнор cookies и т.д.)
// Варианты export const revalidate:export const revalidate = 0; // Как force-dynamicexport const revalidate = false; // Как force-cache (никогда не обновлять)export const revalidate = 3600; // ISR — обновлять каждый час
// runtime: Edge vs Node.jsexport const runtime = 'edge'; // Edge Functions (Cloudflare Workers-like)export const runtime = 'nodejs'; // Node.js (по умолчанию)
// fetchCacheexport const fetchCache = 'force-no-store'; // Все fetch() без кеша🆚 SSR vs SSG vs ISR vs CSR: Когда что?
Заголовок раздела «🆚 SSR vs SSG vs ISR vs CSR: Когда что?»// SSR — используй когда:// ✅ Данные должны быть СВЕЖИМИ при каждом запросе// ✅ Контент персонализирован (зависит от пользователя/сессии)// ✅ Используешь cookies/headers// ✅ Поисковые результаты (searchParams)
// Примеры SSR:// - Лента новостей (всегда свежие)// - Профиль пользователя (персонализировано)// - Корзина покупок// - Страница поиска// - Dashboard с реальными данными
// SSG — используй когда:// ✅ Контент редко меняется// ✅ Одинаков для всех пользователей// ✅ Нужна максимальная скорость// Примеры: блоги, документация, лендинги
// ISR — используй когда:// ✅ Контент меняется редко, но не статичен// ✅ Нужна скорость + относительная свежесть// Примеры: каталог товаров, новости
// CSR — используй когда:// ✅ Только за логином (SEO не нужен)// ✅ Очень интерактивная (дашборд, редактор)// ✅ Данные меняются в реальном времени🔐 SSR и авторизация
Заголовок раздела «🔐 SSR и авторизация»Типичный паттерн — проверка авторизации в SSR:
import { cookies } from 'next/headers';import { redirect } from 'next/navigation';import { verifyToken } from '@/lib/jwt';
export default async function DashboardPage() { const cookieStore = await cookies(); const token = cookieStore.get('auth-token');
if (!token) { redirect('/login'); // Серверный редирект — нет мигания! }
const payload = await verifyToken(token.value);
if (!payload) { redirect('/login?error=invalid-token'); }
const user = await db.user.findUnique({ where: { id: payload.userId }, include: { profile: true }, });
if (!user) { redirect('/login'); }
return ( <div> <h1>Привет, {user.name}!</h1> <DashboardContent user={user} /> </div> );}// Вся логика авторизации — на сервере! Клиент никогда не видит неавторизованный контент.🚀 Оптимизация SSR: Partial Prerendering (PPR)
Заголовок раздела «🚀 Оптимизация SSR: Partial Prerendering (PPR)»Next.js 14+ предлагает экспериментальную фичу — Partial Prerendering:
const nextConfig = { experimental: { ppr: 'incremental', // или true для всего приложения },};
// app/product/[id]/page.tsxexport const experimental_ppr = true;
import { Suspense } from 'react';
export default function ProductPage({ params }) { return ( <div> {/* СТАТИЧЕСКАЯ часть — предрендерится при сборке */} <ProductBreadcrumbs /> <StaticProductInfo />
{/* ДИНАМИЧЕСКАЯ часть — потокова при запросе */} <Suspense fallback={<PriceSkeleton />}> <DynamicPrice productId={params.id} /> {/* Реальная цена */} </Suspense>
<Suspense fallback={<InventorySkeleton />}> <InventoryStatus productId={params.id} /> {/* Наличие */} </Suspense> </div> );}// Статическая оболочка + динамические "дырки" = лучшее из обоих миров!🎯 Резюме урока
Заголовок раздела «🎯 Резюме урока»- Динамический рендеринг включается автоматически при:
cookies(),headers(),searchParams,noStore() - force-dynamic — явный способ включить SSR
- Streaming — HTML отправляется по частям через
<Suspense> - Edge Runtime — выполнение на CDN-узлах (быстрее, но меньше APIs)
- PPR — комбинация статики и динамики в одной странице (future!)