nmk

Лекція №17 (2 години). Next.js та Server-Side Rendering (SSR): Еволюція React до Full-Stack

План лекції

  1. Проблема порожнього index.html: Чому класичний Client-Side React програє в SEO та FCP.
  2. Знайомство з Next.js (Фреймворк на базі React): Що він дає “з коробки”.
  3. Стратегія SSG (Static Site Generation): Генерація під час білду (відмінно для блогів).
  4. Стратегія SSR (Server-Side Rendering): Генерація на кожен запит (відмінно для персоналізованих даних).
  5. Стратегія ISR (Incremental Static Regeneration): Гібридний підхід (оновлення статики у фоні).
  6. Гідратація (Hydration): Як мертвий HTML стає “живим” React-додатком.
  7. Революція React 18 / Next.js 13+: React Server Components (RSC) та App Router.

Перелік умовних скорочень

Вступ

Огляд фреймворку Next.js як розширення можливостей React. Порівняння стратегій рендерингу: Static Site Generation (SSG), Server-Side Rendering (SSR) та Incremental Static Regeneration (ISR). Вивчення концепції React Server Components (RSC) як майбутнього веб-розробки.

Класичний React є клієнтською бібліотекою (Client-Side Rendering). Це означає, що при першому запиті до сайту браузер користувача отримує практично порожній HTML-документ (<div id="root"></div>) і важкий файл bundle.js. Поки цей JavaScript не завантажиться, не розпарситься і не виконається V8-рушієм, користувач бачить білий екран. Крім того, пошукові боти (Googlebot) історично погано індексували такі сторінки. Для вирішення цих кардинальних інженерних проблем у 2016 році компанія Vercel створила Next.js — повноцінний фреймворк поверх бібліотеки React, який дозволив перенести частину процесу рендерингу (обчислення JSX) назад на сервер. Ця лекція присвячена стратегіям рендерингу, які є фундаментом сучасної архітектури високонавантажених Frontend-систем.


1. Проблема CSR (Client-Side Rendering) та FCP

Уявімо інтернет-магазин на класичному React (CSR). Шлях користувача:

  1. Завантаження HTML (10ms) -> екран порожній.
  2. Завантаження JS-бандлу (1000ms) -> екран порожній.
  3. Виконання React, Primary Render (200ms) -> з’являється Loading Spinner.
  4. Мережевий запит з useEffect за товарами (800ms).
  5. Secondary Render з даними (50ms) -> нарешті з’являються товари.

Метрика FCP (First Contentful Paint) — час до того, як користувач побачить першу осмислену намальовану деталь, складає більше 2-х секунд! Для мобільних інтернет-мереж це призводить до відтоку 50% клієнтів ще до завантаження сайту. SEO боти також “бачать” перші 2 секунди порожній div, і сайт втрачає позиції в Google.

Щоб вирішити це, ми маємо повернути користувачу готовий HTML з товарами ще на кроці №1. Для цього і потрібен Next.js.


2. Що таке Next.js?

Next.js — це фреймворк для Production-рішень. Офіційна команда React у своїй документації рекомендує розпочинати нові проєкти саме з Next.js (чи Remix), а не з create-react-app або vite.

Що Next.js дає “з коробки”, чого не має голий React:


3. Стратегія SSG (Static Site Generation)

SSG — це коли HTML генерується ТІЛЬКИ ОДИН РАЗ — ПІД ЧАС БІЛДУ ДОДАТКУ (на етапі npm run build на CI/CD сервері), і потім цей готовий index.html віддається всім мільйонам користувачів з блискавичною швидкістю через CDN (Content Delivery Network).

Ідеально для: Блогів, сторінок “Про компанію”, Документації, Посадкових сторінок (Landing pages). Мінус: Якщо ви опублікували нову статтю в базу даних, вам потрібно повністю “перебілдити” весь додаток, щоб вона з’явилася на сайті.

(Приклад на базі класичного Pages Router)

// Ця сторінка відрендериться 1 РАЗ при деплої
export default function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// Ця функція викликається Next.js ТІЛЬКИ ПІД ЧАС КОМПІЛЯЦІЇ СЕРВЕРА
export async function getStaticProps() {
  const res = await fetch("https://api.github.com/repos/vercel/next.js/issues");
  const posts = await res.json();

  // Повертаємо пропси, з якими Next.js "заморозить" HTML
  return { props: { posts } };
}

4. Стратегія SSR (Server-Side Rendering)

SSR — це коли HTML генерується КОЖЕН РАЗ, КОЛИ КОРИСТУВАЧ РОБИТЬ ЗАПИТ ДО СТОРІНКИ (у реальному часі на сервері Node.js).

Ідеально для: Стрічки новин (Twitter/X), Кошика користувача, Інформашбних панелей адміністратора, будь-яких даних, що критично прив’язані до Cookie авторизації або метрик у реальному часі. Мінус: Високе навантаження на сервер (CPU). Коментар Time-To-First-Byte (TTFB) збільшується, бо серверу треба час на виконання fetch до бази і генерації стрінгів HTML.

export default function Dashboard({ userStats }) {
  return <div>Рівень: {userStats.level}</div>;
}

// Функція викликатиметься ПРИ КОЖНОМУ заході будь-якого користувача
export async function getServerSideProps(context) {
  // context.req містить cookies, заголовки, IP адресу
  const sessionUser = await checkAuthTokens(context.req.cookies.token);

  const res = await fetch(`https://api.mygame.com/stats/${sessionUser.id}`);
  const userStats = await res.json();

  return { props: { userStats } };
}

5. Стратегія ISR (Incremental Static Regeneration)

ISR — це інженерний шедевр Next.js. Це спроба взяти блискавичну швидкість SSG і поєднати з динамікою SSR. При ISR ви генеруєте сторінки як статичні (SSG), але паралельно “кажете” Next.js: “оновлюй ці сторінки у фоні на сервері кожні X секунд, якщо будуть надходити запити”.

Ідеально для: Карток товарів в інтернет-магазині (ціна змінюється рідко, але потрібно, щоб протягом дня вона підтягнулася без перебілду сайту).

export async function getStaticProps() {
  const res = await fetch("https://api.store.com/product/123");
  const product = await res.json();

  return {
    props: { product },
    // МАГІЯ ISR:
    // Next.js буде віддавати кешований HTML з білду перші 60 секунд.
    // Якщо прийде користувач на 61-й секунді, Next віддасть йому старий HTML,
    // АЛЕ у фоні ініціює новий запит на API, згенерує новий HTML і збереже його в кеш.
    // Всі наступні користувачі отримають вже новий HTML.
    revalidate: 60,
  };
}

6. Гідратація (Hydration)

Одне з найскладніших питань на Senior-інтерв’ю: “Як працює Гідратація?” Коли браузер отримує HTML від Next.js (через SSR або SSG), він бачить просто місиво візуальних тегів і текстів. Наприклад, кнопка <button class="btn">Click me</button> є візуально на екрані, але вона мертва, вона не має прив’язаної JavaScript функції onClick, бо браузер ще не знає, що це код на React.

Процес “воскресіння” сторінки називається Гідратацією (Hydration):

  1. Браузер малює HTML миттєво (FCP = 100ms).
  2. Браузер у фоні дозавантажує React та JS-бандл з розробленою логікою.
  3. React запускається у браузері і звіряє (Reconciliation) свій Virtual DOM з тим реальним HTML, що вже показаний на екрані.
  4. Наклавши Virtual DOM на реальний, React “навішує” всі Event Listeners (onClick, onChange).
  5. Сторінка стає інтерактивною (Time to Interactive - TTI).

7. Революція React 18 / Next.js 13+: React Server Components (RSC)

Дійшли до сучасності. Використання SSR та Гідратації має фундаментальний недолік: нам доводиться відправляти ВЕСЬ JavaScript код компонентів на клієнт (разом з HTML), щоб React міг провести гідратацію. Чим складніший UI, тим товще бандл (Megabytes of JS).

Впровадження архітектури App Router у Next.js 13 реалізувало концепцію React Server Components (RSC). RSC — це компоненти, які рендеряться виключно на сервері. Їх JavaScript код (і код їхніх бібліотек, як наприклад date-fns чи Markdown-парсерів) НІКОЛИ не відправляється в браузер.

Якщо вам потрібна кнопка з onClick, ви створюєте звичайний “Клієнтський” компонент (через директиву "use client";) і вставляєте його всередину Серверного компонента як острівець інтерактивності (Islands Architecture).

// app/page.tsx (Server Component за замовчуванням у Next.js API Router)
// ЦЕЙ КОД І БІБЛІОТЕКИ ВИКОНУЮТЬСЯ ТІЛЬКИ НА СЕРВЕРІ! НІЯКОГО JS У БРАУЗЕРІ ПРО ЦЕЙ КОМПОНЕНТ.
import db from "./database";
import ClientLikeButton from "./ClientLikeButton";

export default async function ProductPage() {
  // ПРЯМИЙ ЗАПИТ У БАЗУ (без Axios/Fetch Api Layer!)
  const product = await db.query("SELECT * FROM products WHERE id=1");

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      {/* Клієнтський острівець для інтерактивності (тільки його JS код полетить у браузер) */}
      <ClientLikeButton id={product.id} />
    </div>
  );
}

Підсумок RSC: Ми отримуємо найкраще з обох світів — блискавичний HTML з прямим доступом до бази на сервері і нульовим розміром JS бандлу, при цьому зберігаємо клієнтську реактивність React-у в місцях “острівців” (кнопок, форм).


Висновки

  1. Переваги CSR (швидкі переходи між сторінками) на початковому етапі React нівелювалися критичними втратами в SEO та метриці FCP (швидкість відмальовки). Next.js з’явився як мета-фреймворк для вирішення цієї інженерної дилеми “тонкого клієнта”.
  2. Методологія SSG (генерація в build-time) використовується архітекторами для статичних інформаційних порталів. Цей вид доставки (через CDN-вузли) гарантує найвищу можливу швидкість, проте не витримує системи, де контент динамічно змінюється кожну секунду.
  3. SSR переносить виклик рендер-функцій React та fetch-запитів на сервера Node.js, генеруючи свіжий HTML на кожне звернення браузера. Це вирішує проблему персоналізації, проте підвищує фінансові витрати на оренду серверних потужностей (Computational Cost).
  4. Процес Гідратації — це “оживлення” статичного мертвого HTML-каркасу шляхом навішування JavaScript Event Listeners з боку React-рушія на клієнті. Недоліком є відчутний провал між моментом “користувач побачив кнопку” (FCP) і “користувач може на неї натиснути” (TTI).
  5. Парадигма React Server Components (RSC) з App Router у новітньому Next.js ламає класичну модель. RSC дозволяє розробнику звертатися до Бази Даних прямо з JSX та взагалі не відправляти код цього компонента (модуля) в браузер користувача, радикально зменьшуючи Bundle Size JavaScript-у додатку на сотні кілобайт.

Джерела

  1. Офіційний довідник Next.js “Routing Fundamentals / Server Components”. URL: https://nextjs.org/docs/app/building-your-application/routing
  2. Офіційний довідник Next.js “Data Fetching & Caching”. URL: https://nextjs.org/docs/app/building-your-application/data-fetching
  3. “Understanding React Server Components”. Vercel Blog. URL: https://vercel.com/blog/understanding-react-server-components
  4. Dan Abramov. “React Server Components Proposal”. Github Discussions.
  5. “Hydration and Server-side Rendering”. K.C. Dodds. URL: https://kentcdodds.com/blog/super-simple-start-to-remix (Пояснення різниці SSG/SSR)
  6. “Core Web Vitals” (Метрики FCP, TTI). Google Developers. URL: https://web.dev/vitals/

Запитання для самоперевірки

  1. З технічної точки зору парсингу (перетворення байтів HTML у дерево DOM), поясніть конкретну причину, чому пошукові боти можуть індексувати сайти на чистому React (CRA) значно гірше, ніж сайти на Next.js.
  2. Яким чином стратегія ISR (Incremental Static Regeneration) долає обмеженість SSG методу щодо “старості” (Staleness) даних впродовж довготривалої експлуатації великого e-commerce майданчика? Що відбувається у фоні на сервері Next.js?
  3. Процес “Гідратації”: Що відбудеться з користувацьким досвідом (UX), якщо користувач натисне на кнопку “Додати в кошик”, відрендерену через SSR, на повільному (3G) інтернет-зв’язку у проміжок часу між закінченням завантаження HTML (1-ша секунда) та початком завантаження файлу bundle.js (5-та секунда)?
  4. Чому використання React-хуків useState та useContext фізично заборонене (буде викликана помилка інструментом компіляції) в архітектурі React Server Components (RSC)? Опишіть обмеження серверного середовища для таких стейтів.
  5. Що таке патерн “Islands Architecture” (Архітектура Острівців) в розробці сучасних веб-додатків на базі Next.js 13+? Наведіть приклад компонента, який ви б зробили “клієнтським острівцем” ("use client";) на повністю серверній сторінці перегляду “Новини”.