Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «CSR, SSR, SSG, ISR — різниця між стратегіями рендерингу». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**CSR, SSR, SSG, ISR** - стратегії рендерингу, які визначають коли і де збирається HTML: браузер (CSR), сервер при кожному запиті (SSR), час збірки (SSG), або кеш з періодичним оновленням (ISR). ```tsx export const revalidate = 60; // ISR: кеш, перезбірка кожні 60с // { cache: 'no-store' } // SSR: свіжий при кожному запиті // useEffect(() => fetch(), []) // CSR: порожня оболонка, JS заповнює ``` **Головне:** SSG для блогів і документації, SSR для персоналізованих сторінок, ISR для e-commerce, CSR для адмін-панелей.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**CSR, SSR, SSG та ISR** - чотири стратегії рендерингу, які визначають коли і де збирається HTML: у браузері після завантаження JS, на сервері при кожному запиті, під час збірки як статичні файли, або як кешована сторінка що оновлюється за таймером. ## Теорія ### TL;DR - CSR = сервер надсилає `<div id="root"></div>`, браузер завантажує JS, робить запити до API і будує сторінку. До виконання JS - нічого не видно. - SSR = сервер генерує повний HTML при кожному запиті. Браузер одразу відображає сторінку, JS підключається після через гідрацію (hydration). - SSG = HTML збирається один раз під час `npm run build` і роздається як статичні файли з CDN. Сервер не задіяний в рантаймі. - ISR = SSG з таймером або тригером ревалідації (revalidation). Кешована версія роздається одразу, нова збирається у фоні коли стара застаріла. - Правило вибору: статичний контент - SSG, персоналізовані сторінки - SSR, сторінки товарів - ISR, адмін-панелі - CSR. ### Швидкий приклад ```tsx // Next.js 14 App Router - всі чотири стратегії // SSG (за замовчуванням): збирається один раз, роздається з CDN export default async function StaticPage() { const res = await fetch('https://api.example.com/posts'); // кешується при збірці const data = await res.json(); return <div>{data.title}</div>; } // ISR: кешується, перезбирається кожні 60 секунд export const revalidate = 60; // SSR: свіжий HTML при кожному запиті const res = await fetch('https://api.example.com/user', { cache: 'no-store' }); // CSR: сервер надсилає порожню оболонку, useEffect заповнює її 'use client'; useEffect(() => { fetch('/api/data').then(setData); }, []); ``` SSG та ISR роздають готовий HTML з CDN. SSR генерує HTML на сервері при кожному запиті. CSR надсилає порожню оболонку і перекладає всю роботу на браузер. ### Головна різниця Вся суть у часі та місці. CSR рендерить все у браузері після завантаження JS, тому користувач спочатку бачить порожній екран. SSR і SSG обидва надсилають готовий HTML, звідси і краще SEO та швидший First Contentful Paint. Різниця між SSG та ISR - це свіжість контенту: SSG-сторінки не змінюються без перезбірки, ISR-сторінки оновлюються у фоні без деплою, за таймером або вебхуком від CMS. ### Коли що використовувати - Блог, документація, маркетингові сторінки: SSG. Контент змінюється рідко, CDN-доставка безкоштовно дає швидкість. - Дашборд, стрічка активності, сторінки за авторизацією: SSR. Дані специфічні для користувача, кешування на рівні CDN не допомагає. - Сторінки товарів (ціна, наявність): ISR з `revalidate: 60` або on-demand через `revalidateTag('product')`. Stale-while-revalidate тримає час відповіді малим. - Адмін-панелі, внутрішні інструменти, SPA: CSR. SEO не важливе, зате повна клієнтська інтерактивність без overhead від гідрації. - Змішаний застосунок (документація плюс пошук плюс профіль): гібрид. SSG для статичних маршрутів, SSR для персоналізованих, CSR для інтерактивних віджетів. ### Таблиця порівняння | Аспект | CSR | SSR | SSG | ISR | |---|---|---|---|---| | Де рендериться | Браузер (JS-бандл) | Сервер при кожному запиті | Під час збірки | Збірка + фонова ревалідація | | Початковий HTML | Порожній `<div id="root">` | Повний, семантичний | Повний, семантичний | Повний, кешований (оновлюється пізніше) | | TTFB | Швидкий (малий HTML) | Повільніший (обчислення сервера) | Найшвидший (CDN) | Швидкий (кеш, ревалідація у фоні) | | SEO | Погане | Відмінне | Відмінне | Відмінне | | Навантаження на сервер | Відсутнє після деплою | Високе при кожному запиті | Відсутнє в рантаймі | Низьке (інкрементальні збірки) | | Динамічні дані | API-запити з браузера | Fetch на сервері при запиті | Потрібна повна перезбірка | Таймер або API-тригер | | Інструменти | Vite + React, Create React App | Next.js, Remix | Gatsby, Next.js static export | Next.js `revalidate` | | Найкраще для | SPA, адмін-панелі | Персоналізовані сторінки | Блоги, документація | Новини, сторінки товарів | ### Як це працює зсередини **CSR:** браузер отримує мінімальний HTML, завантажує JS-бандл, V8 у Chrome парсить і виконує його, React-рекнсиляр будує віртуальний DOM і записує в реальний. Нічого видимого до завершення цього циклу. На повільному з'єднанні - кілька секунд білого екрану. **SSR:** Node.js запускає `renderToString()` або `renderToPipeableStream()` (стримінг у React 18) на сервері і надсилає готовий HTML у відповіді. Браузер одразу відображає сторінку. Потім завантажується JS-бандл і React виконує гідрацію: прикріплює обробники подій до існуючого HTML без повторного рендеру. Якщо клієнт і сервер генерують різний HTML, з'являється попередження про мismatch гідрації і React перерендерює дерево компонентів з нуля. **SSG:** під час `next build` Next.js попередньо рендерить кожну статичну сторінку і записує HTML-файли на диск. Ці файли потрапляють на CDN. Кожен користувач отримує відповідь від найближчого CDN-вузла, сервер не задіяний. **ISR:** так само як SSG, але кожна сторінка має TTL. Після його закінчення наступний запит запускає фонову перезбірку. Кешована версія продовжує роздаватися, поки нова збирається, потім атомарно замінює кеш. Next.js також підтримує on-demand ревалідацію через `revalidatePath()` або `revalidateTag()` - корисно, коли CMS публікує новий контент і потрібно оновити сторінку за секунди, не чекаючи таймера. ### Типові помилки **SSR для статичного контенту.** Розробник додає SSR до блогу, кожен перегляд сторінки навантажує сервер. Рахунок за хостинг зростає без причини. Виправлення: `export const dynamic = 'force-static'` у Next.js або перехід на SSG. Ніщо в блозі не потребує рендерингу при кожному запиті. **Забутий revalidate на сторінках товарів.** За замовчуванням Next.js поводиться як SSG без ревалідації, ціни застарівають і ніхто не помічає до першої скарги покупця. Виправлення: ```tsx // app/products/[id]/page.tsx export const revalidate = 300; // перезбираємо кожні 5 хвилин // app/api/revalidate/route.ts - CMS викликає цей вебхук при публікації import { revalidateTag } from 'next/cache'; export async function POST(request: Request) { const { productId } = await request.json(); revalidateTag(`product-${productId}`); // перезбирає лише цю сторінку товару return Response.json({ revalidated: true }); } ``` **CSR без розділення коду (code splitting).** Бандл на 2MB означає 10+ секунд до інтерактивності. React парсить і виконує все одразу. Виправлення: `React.lazy` з `Suspense`: ```tsx const ProductList = lazy(() => import('./ProductList')); // Завантажується лише коли компонент потрібен, не при першому завантаженні ``` **Мismatch гідрації в SSR.** Клієнт рендерить щось відмінне від того, що надіслав сервер - зазвичай через timestamps, випадкові ID або звернення до `window`. React 18 логує попередження і перерендерює все дерево компонентів з нуля на клієнті. Виправлення: клієнтська логіка в `useEffect`, або `suppressHydrationWarning` на елементах з відомими розбіжностями. **SSG з некешованим зовнішнім API.** Збірка падає або виходить час на Vercel, коли зовнішній сервіс повільний або застосовує rate limiting. Виправлення: `{ next: { revalidate: false } }` для постійного кешування при збірці, або мок-дані для build-часу і ISR для рантайму. ### Де зустрічається в реальних проектах - Next.js 14 Vercel Commerce: ISR з `revalidate` для сторінок товарів, SSG для категорій і лендінгів. - Gatsby: SSG для блогу Netflix і великих документаційних сайтів, статичний експорт на S3 + CloudFront. - Remix: SSR для Shopify Hydrogen (динамічні кошики, сесії, персоналізація). - Nuxt 3: універсальний SSR і SSG для Vue-застосунків, зокрема Nuxt Content docs. - SvelteKit: `prerender = true` для статичних маршрутів, SSR для динамічних в одному застосунку. Я бачив команди, які за замовчуванням обирали SSR для всього і потім дивувалися, чому рахунок за сервер потроювався. Рішення майже завжди одне - перенести сторінки, яким не потрібна свіжість при кожному запиті, на ISR або SSG. ### Питання на співбесіді **Q:** Як працює гідрація (hydration) в SSR і що спричиняє мismatch? **A:** React порівнює HTML від сервера з тим, що виробив би клієнтський рендер. Якщо вони відрізняються - через timestamp, `Math.random()` або звернення до `window` - React логує попередження і перерендерює дерево компонентів з нуля у браузері. Виправлення: клієнтська логіка в `useEffect`, або `suppressHydrationWarning` на елементі. **Q:** Які реальні показники LCP для кожної стратегії? **A:** SSG та ISR з CDN показують LCP менше 100ms. SSR - 200-500ms залежно від розташування сервера та часу запиту до бази. CSR - у середньому 2-5 секунд, бо браузер спочатку завантажує, парсить і виконує весь JS-бандл. **Q:** Як ISR поводиться при піку трафіку під час ревалідації? **A:** Кешована версія роздається всім користувачам, поки фонова перезбірка виконується. Одночасно запускається лише одна перезбірка, незалежно від кількості запитів. Після завершення нова версія атомарно замінює кеш. **Q:** Яка різниця між ISR та Partial Prerendering (PPR) у Next.js 15? **A:** ISR перезбирає всю сторінку за таймером або тригером. PPR (експериментальна функція в Next.js 15) попередньо рендерить статичну оболонку при збірці та заповнює динамічні "дірки" під час запиту без перезбірки всієї сторінки. Для сторінки товару, де опис статичний, а кількість товарів у кошику динамічна, PPR швидший. **Q:** Чим CSR відрізняється від islands architecture? **A:** Повний CSR відправляє весь JS-бандл у браузер і рендерить все на клієнті. Islands architecture (Astro, Fresh) попередньо рендерить статичну оболонку як звичайний HTML і гідратує лише інтерактивні острівці - поле пошуку, кошик, лічильник. Решта залишається як HTML без JS. Результат: менший бандл, швидший час до інтерактивності і краще SEO ніж у CSR. ## Приклади ### CSR: клієнтський компонент з отриманням даних ```tsx // Vite + React - повний CSR, рендеринг на сервері відсутній import { useState, useEffect } from 'react'; function UserProfile({ userId }: { userId: string }) { const [user, setUser] = useState<{ name: string; email: string } | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => { setUser(data); setLoading(false); }); }, [userId]); if (loading) return <div>Завантаження...</div>; return <div>{user!.name} - {user!.email}</div>; } // Що надсилає сервер: <div id="root"></div> // Що бачить користувач спочатку: нічого (або спінер) // Що бачить SEO-краулер: порожній div ``` Сервер не надсилає нічого корисного. Браузер завантажує JS, виконує його, робить API-запит, чекає відповіді і лише тоді рендерить. Саме тому CSR показує стан завантаження, а SSR - ні. ### ISR: сторінка товару з on-demand ревалідацією ```tsx // app/products/[id]/page.tsx - Next.js 14 App Router export const revalidate = 60; // перезбираємо не частіше ніж раз на 60 секунд async function getProduct(id: string) { const res = await fetch( `https://api.example.com/products/${id}`, { next: { tags: ['product', `product-${id}`] } } // тег для точкової ревалідації ); return res.json(); } export default async function ProductPage({ params }: { params: { id: string } }) { const product = await getProduct(params.id); return ( <div> <h1>{product.name}</h1> <p>{product.price} грн</p> <p>{product.inStock ? 'В наявності' : 'Немає в наявності'}</p> </div> ); } // app/api/revalidate/route.ts - CMS викликає цей вебхук при оновленні товару import { revalidateTag } from 'next/cache'; export async function POST(request: Request) { const { productId } = await request.json(); revalidateTag(`product-${productId}`); return Response.json({ revalidated: true }); } ``` Сторінка відповідає з кешу за менше ніж 10ms. Коли ціна змінюється в CMS, CMS викликає ревалідаційний ендпоінт, Next.js перезбирає лише цю сторінку у фоновому режимі, і нова версія замінює кеш. Деплой не потрібен. ### SSR проти ISR: коли різниця має значення ```tsx // Варіант А: SSR - свіжий HTML при кожному запиті async function getProduct(id: string) { const res = await fetch( `https://api.example.com/products/${id}`, { cache: 'no-store' } // вимикає весь кеш ); return res.json(); } // Час відповіді: 200-500ms при кожному запиті // Підходить для: real-time залишків, персональних цін, даних кошика // Варіант Б: ISR - кешується, перезбирається кожні 60 секунд export const revalidate = 60; async function getProduct(id: string) { const res = await fetch( `https://api.example.com/products/${id}`, { next: { revalidate: 60 } } ); return res.json(); } // Час відповіді: менше 10ms з CDN протягом 60 секунд, потім одна фонова перезбірка // Підходить для: описів товарів, цін що оновлюються кілька разів на день ``` Якщо точність ціни важлива з точністю до секунди - SSR. Якщо вікно застарілості в 60 секунд прийнятне і сторінка має витримувати тисячі запитів на секунду - ISR. Більшість сторінок товарів цілком підходять під ISR з вебхуком від CMS для термінових оновлень.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.