Пошук уроків, статей та іншого контенту
Три абревіатури, що описують, коли й де рендериться сторінка — і чому в App Router вони працюють інакше, ніж в старому Pages Router.
У Next.js абревіатури SSR, SSG та ISR описують не стільки різні фреймворки, скільки момент і місце підготовки HTML:
SSR — сторінка рендериться на сервері під час запиту.
SSG — сторінка генерується заздалегідь, зазвичай під час збірки.
ISR — статично згенерована сторінка оновлюється через певний час або після явної інвалідації кешу.
У Pages Router ці режими були чітко представлені окремими функціями. В App Router Next.js використовує іншу модель: статичний або динамічний рендеринг визначається конфігурацією маршруту, способом отримання даних та використанням динамічних API.
Для кожного маршруту важливо відповісти на два запитання:
Де виконується код — на сервері чи в браузері?
Коли формується результат — під час збірки, під час запиту чи після завершення терміну кешування?
У Next.js компонент за замовчуванням виконується на сервері, якщо він знаходиться в app і не позначений директивою "use client".
Це не означає, що кожна сторінка автоматично є SSR. Серверний компонент може бути:
відрендерений статично;
відрендерений динамічно для кожного запиту;
включений у сторінку, яка частково використовує кешовані дані.
Тому коректніше розділяти:
серверне виконання компонентів;
статичний або динамічний спосіб рендерингу маршруту;
кешування даних і готового HTML/RSC-результату.
SSR означає, що сервер створює результат сторінки під час кожного запиту або кожного некешованого запиту.
Типовий сценарій:
Користувач відкриває сторінку.
Next.js отримує запит.
Сервер завантажує актуальні дані.
Сервер формує HTML.
HTML надсилається браузеру.
За потреби React гідратує інтерактивні компоненти.
SSR добре підходить для даних, які:
залежать від користувача;
залежать від cookies або заголовків;
повинні бути актуальними в момент запиту;
змінюються надто часто для ефективного статичного кешування.
Приклади:
особистий кабінет;
кошик покупця;
сторінка з результатами пошуку;
контент, залежний від регіону або сесії;
перевірка прав доступу.
У старому pages-підході SSR зазвичай реалізовували через getServerSideProps.
export async function getServerSideProps(context) {
const response = await fetch("https://api.example.com/profile", {
headers: {
cookie: context.req.headers.cookie || "",
},
});
const profile = await response.json();
return {
props: {
profile,
},
};
}
export default function ProfilePage({ profile }) {
return <h1>Вітаємо, {profile.name}</h1>;
}Функція getServerSideProps виконується на сервері для кожного запиту.
В App Router немає getServerSideProps. Серверний компонент може отримати дані безпосередньо всередині сторінки:
export default async function ProfilePage() {
const response = await fetch("https://api.example.com/profile", {
cache: "no-store",
});
const profile = await response.json();
return <h1>Вітаємо, {profile.name}</h1>;
}Опція cache: "no-store" повідомляє Next.js, що результат цього запиту не потрібно зберігати в кеші. У такому випадку маршрут зазвичай стає динамічним.
Інший спосіб явно вказати динамічний рендеринг:
export const dynamic = "force-dynamic";Однак у більшості випадків достатньо правильно налаштувати джерела даних і динамічні API.
SSG означає, що сторінка генерується заздалегідь і може обслуговуватися як готовий статичний результат.
Переваги SSG:
швидка відповідь;
просте CDN-кешування;
менше роботи для сервера під час запиту;
хороша продуктивність для публічного контенту;
зручність для документації, блогів і маркетингових сторінок.
SSG підходить для:
статей;
сторінок документації;
описів товарів, які змінюються рідко;
публічних сторінок із відомими URL;
сторінок, для яких не потрібна персоналізація.
У Pages Router для статичної генерації використовували getStaticProps.
export async function getStaticProps() {
const response = await fetch("https://api.example.com/articles");
const articles = await response.json();
return {
props: {
articles,
},
};
}
export default function ArticlesPage({ articles }) {
return (
<main>
{articles.map((article) => (
<article key={article.id}>{article.title}</article>
))}
</main>
);
}getStaticProps виконується під час збірки. Дані потрапляють у заздалегідь згенеровану сторінку.
Для динамічних маршрутів разом із getStaticProps використовували getStaticPaths:
export async function getStaticPaths() {
const response = await fetch("https://api.example.com/articles");
const articles = await response.json();
return {
paths: articles.map((article) => ({
params: { slug: article.slug },
})),
fallback: "blocking",
};
}В App Router звичайний серверний компонент із кешованими даними може бути статичним:
export default async function ArticlesPage() {
const response = await fetch("https://api.example.com/articles", {
cache: "force-cache",
});
const articles = await response.json();
return (
<main>
{articles.map((article) => (
<article key={article.id}>{article.title}</article>
))}
</main>
);
}У багатьох випадках fetch у серверному компоненті можна залишити без явного cache, якщо потрібна поведінка відповідає налаштуванням версії Next.js і конкретному маршруту. Проте для важливих сценаріїв краще явно визначати стратегію кешування.
Для динамічних сегментів маршруту використовують generateStaticParams:
export async function generateStaticParams() {
const response = await fetch("https://api.example.com/articles");
const articles = await response.json();
return articles.map((article) => ({
slug: article.slug,
}));
}
export default async function ArticlePage({ params }) {
const { slug } = await params;
const response = await fetch(
`https://api.example.com/articles/${slug}`,
{
cache: "force-cache",
},
);
const article = await response.json();
return <article>{article.title}</article>;
}generateStaticParams є аналогом getStaticPaths для App Router, але використовується разом зі структурою каталогів app.
ISR поєднує переваги SSG та актуальність SSR.
Сторінка спочатку генерується статично. Після завершення визначеного періоду Next.js може оновити її, отримавши свіжі дані.
Наприклад:
сторінка згенерована о 12:00;
для неї встановлено revalidate: 60;
до 12:01 користувачі отримують кешований результат;
після завершення 60 секунд Next.js може виконати повторне отримання даних і сформувати нову версію.
Цей підхід часто називають stale-while-revalidate: деякий час користувачі отримують попередню версію, а кеш оновлюється у фоновому режимі.
У Pages Router ISR налаштовували через поле revalidate у результаті getStaticProps.
export async function getStaticProps() {
const response = await fetch("https://api.example.com/news");
const news = await response.json();
return {
props: {
news,
},
revalidate: 60,
};
}
export default function NewsPage({ news }) {
return (
<main>
{news.map((item) => (
<article key={item.id}>{item.title}</article>
))}
</main>
);
}Тут сторінка статично генерується, але може бути оновлена не частіше ніж раз на 60 секунд.
В App Router періодичне оновлення можна вказати безпосередньо для fetch:
export default async function NewsPage() {
const response = await fetch("https://api.example.com/news", {
next: {
revalidate: 60,
},
});
const news = await response.json();
return (
<main>
{news.map((item) => (
<article key={item.id}>{item.title}</article>
))}
</main>
);
}Або встановити час повторної валідації для всього сегмента маршруту:
export const revalidate = 60;Це зручно, коли всі кешовані дані на сторінці повинні оновлюватися за однаковим інтервалом.
Іноді не потрібно чекати 60 секунд або інший заданий інтервал. Наприклад, адміністратор щойно опублікував статтю, і сайт повинен показати її негайно.
Для цього використовують явну інвалідацію кешу:
revalidatePath — інвалідує кеш конкретного маршруту;
revalidateTag — інвалідує дані, позначені певним тегом.
Приклад із тегом:
const response = await fetch("https://api.example.com/articles", {
next: {
tags: ["articles"],
},
});Після зміни даних у серверній функції можна інвалідувати цей тег:
import { revalidateTag } from "next/cache";
export async function publishArticle() {
// Публікація статті у зовнішній системі
revalidateTag("articles");
}Назви та спосіб виклику серверної функції залежать від архітектури застосунку. Головна ідея полягає в тому, що кеш можна оновлювати не лише за часом, а й у відповідь на подію.
У старому Pages Router режим сторінки переважно визначався функцією:
getServerSideProps — SSR;
getStaticProps — SSG або ISR;
відсутність цих функцій — клієнтський рендеринг або звичайна сторінка без серверного отримання даних.
В App Router рішення більш деталізоване. На динамічність можуть впливати:
fetch із cache: "no-store";
fetch із відповідними налаштуваннями кешування;
cookies();
headers();
searchParams;
параметри маршруту;
конфігурація сегмента dynamic;
інші API, які залежать від конкретного запиту.
Наприклад, використання cookies робить результат залежним від поточного користувача:
import { cookies } from "next/headers";
export default async function AccountPage() {
const cookieStore = await cookies();
const session = cookieStore.get("session");
if (!session) {
return <p>Потрібно увійти в систему.</p>;
}
return <h1>Особистий кабінет</h1>;
}Таку сторінку не можна безпечно віддавати як одну спільну статичну версію для всіх користувачів.
"use client" використання CSRНі.
"use client" означає, що компонент є клієнтським і може використовувати:
стан React;
обробники подій;
браузерні API;
хуки, які доступні лише на клієнті.
Але сторінка все одно може отримати початковий HTML із сервера. Після цього клієнтський компонент буде гідратований у браузері.
Наприклад, інтерактивна кнопка може бути клієнтським компонентом усередині статично згенерованої сторінки:
"use client";
import { useState } from "react";
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "Подобається" : "Подобається?"}
</button>
);
}Отже:
SSR, SSG та ISR описують переважно серверне формування і кешування сторінки;
Client Component описує місце виконання конкретного компонента та його інтерактивність.
Це різні виміри архітектури.
Використовуйте SSR, коли:
дані персональні;
результат залежить від cookies або заголовків;
потрібна актуальність під час кожного запиту;
сторінка не може бути спільною для всіх відвідувачів.
Недоліки:
більше навантаження на сервер;
потенційно більший час відповіді;
менше можливостей для довгого CDN-кешування.
Використовуйте SSG, коли:
контент однаковий для більшості користувачів;
дані змінюються рідко;
сторінки можна підготувати заздалегідь;
важлива максимальна швидкість віддачі.
Недоліки:
нові сторінки або зміни можуть з’явитися лише після нової збірки;
велика кількість сторінок збільшує час збірки;
персоналізовані дані не підходять для повністю статичного результату.
Використовуйте ISR, коли:
контент публічний;
дані змінюються періодично;
не потрібно перебудовувати весь застосунок після кожної зміни;
допустима невелика затримка актуалізації.
Недоліки:
користувачі можуть тимчасово бачити стару версію;
потрібно продумати інвалідацію кешу;
поведінка залежить від платформи розгортання та її кешування.
Зручно почати з характеру даних.
Стаття доступна всім, а зміни відбуваються кілька разів на день.
Підходять:
SSG, якщо сайт перебудовується після публікації;
ISR, якщо потрібно автоматичне оновлення;
ISR із revalidateTag, якщо CMS може повідомити застосунок про зміни.
Профіль залежить від поточного користувача.
Підходить:
динамічний рендеринг;
отримання cookies або сесії на сервері;
за потреби — додаткове клієнтське оновлення даних.
Каталог публічний, але ціни та залишки змінюються.
Можливий комбінований підхід:
назви та описи — кешовані;
ціна — ISR із коротким інтервалом;
залишок — динамічний запит або окреме оновлення;
кошик — персональна клієнтська або серверна логіка.
Не обов’язково вибирати один режим для всього застосунку.
Результат залежить від параметрів запиту та часто має бути актуальним.
Зазвичай підходить:
динамічний серверний рендеринг;
кешування лише там, де воно безпечне;
окреме оброблення популярних або повторюваних запитів.
У Pages Router:
getServerSideProps — SSR;
getStaticProps — SSG або ISR;
getStaticPaths — попередня генерація динамічних шляхів.
В App Router:
асинхронні серверні компоненти;
налаштування fetch;
generateStaticParams;
revalidate;
dynamic;
інвалідація через revalidatePath і revalidateTag.
У Pages Router сторінка часто мала один очевидний режим.
В App Router різні частини маршруту можуть мати різні вимоги до даних. Один серверний компонент може використовувати кешований запит, а інший — персональний динамічний запит.
Це робить модель гнучкішою, але вимагає уважніше аналізувати:
які дані кешуються;
на який строк;
чи є результат персональним;
що саме спричиняє динамічний рендеринг.
Серверний компонент не дорівнює SSR для кожного запиту. Якщо його дані кешуються, маршрут може бути статичним або частково кешованим.
cache: "no-store" всюдиЦе вимикає кешування й може перетворити публічні сторінки на динамічні без потреби.
Спочатку визначте, чи справді дані повинні бути унікальними для кожного запиту.
Дані користувача, сесії, приватні замовлення або права доступу не можна бездумно зберігати у спільному кеші.
Потрібно переконатися, що кешований результат не буде показаний іншому користувачеві.
revalidaterevalidate: 60 не означає, що сторінка перебудовується рівно через 60 секунд після зміни даних. Це інтервал, після якого кеш може бути визнаний застарілим і оновлений під час наступного звернення.
Для негайного оновлення використовуйте явну інвалідацію.
Запит у useEffect або використання клієнтської бібліотеки для даних — це не ISR. Це клієнтське завантаження після початкового рендерингу.
ISR працює на рівні серверного рендерингу та кешування результатів.
"use client" повністю клієнтським рендерингомКлієнтський компонент може бути включений у HTML, сформований сервером. Директива визначає можливості компонента, а не обов’язково весь режим сторінки.
generateStaticParams для всіх можливих шляхів без оцінки масштабуЯкщо каталог містить сотні тисяч або мільйони записів, генерація всіх сторінок під час збірки може бути повільною або недоцільною.
У такому випадку варто обмежити кількість попередньо згенерованих сторінок і продумати поведінку для інших маршрутів, кешування та оновлення даних.
Перед реалізацією сторінки поставте такі запитання:
Чи однаковий результат для всіх користувачів?
Чи залежать дані від cookies, headers або сесії?
Наскільки швидко дані повинні оновлюватися?
Чи допустима затримка в кілька секунд або хвилин?
Чи можна оновити кеш після події з CMS або бекенду?
Чи потрібно генерувати всі динамічні URL під час збірки?
Чи потрібна інтерактивність у браузері окремим частинам сторінки?
Загальна відповідність може виглядати так:
персональні або залежні від запиту дані — динамічний рендеринг;
незмінний публічний контент — SSG;
публічний контент із періодичними змінами — ISR;
інтерактивний інтерфейс — Client Components поверх серверного результату.
SSR, SSG та ISR залишаються корисними поняттями, але в App Router їх не слід сприймати лише як три окремі функції.
SSR — результат формується динамічно під час запиту.
SSG — результат готується заздалегідь і кешується як статичний.
ISR — статичний результат періодично або явно оновлюється.
App Router визначає поведінку через серверні компоненти, кешування fetch, revalidate, динамічні API та інвалідацію кешу.
"use client" відповідає за інтерактивність компонента, а не автоматично за весь спосіб рендерингу сторінки.
Для правильної архітектури потрібно аналізувати не лише сторінку, а й кожен тип даних, який вона використовує.