Pre-rendering та Data Fetching
Next.js Pre-rendering — друкарський верстат що виробляє готові копії сторінок заздалегідь
У попередньому уроці ми створили перший Next.js проект з маршрутизацією. Але справжня сила Next.js -- у тому, як він рендерить сторінки. На відміну від звичайного React, Next.js може генерувати HTML заздалегідь. Це називається pre-rendering.
Три стратегії рендерингу
CSR vs SSR vs SSG: порівняння стратегій рендерингу
CSR -- Client-Side Rendering
Це те, як працює звичайний React-додаток (Vite):
- Сервер відправляє порожній HTML (
<div id="root"></div>) - Браузер завантажує JavaScript
- JavaScript рендерить сторінку на стороні клієнта
- Користувач бачить контент
Проблема: поки JS не завантажиться -- сторінка порожня. Пошукові боти (Google) можуть не дочекатися.
SSR -- Server-Side Rendering
- Користувач робить запит
- Сервер виконує React-код і генерує готовий HTML
- Браузер одразу показує контент
- JavaScript "гідратує" сторінку (додає інтерактивність)
Плюси: контент видно одразу, хороший SEO. Мінуси: кожен запит навантажує сервер.
SSG -- Static Site Generation
- Під час збірки (
npm run build) Next.js генерує HTML - Готові HTML-файли розміщуються на CDN
- Кожен запит отримує заздалегідь готову сторінку
Плюси: найшвидший варіант, мінімальне навантаження. Мінуси: дані "заморожені" з моменту збірки.
Порівняння стратегій
| Критерій | CSR | SSR | SSG |
|---|---|---|---|
| Швидкість першого завантаження | Повільно | Швидко | Найшвидше |
| SEO | Погано | Добре | Добре |
| Навантаження на сервер | Мінімальне | Високе | Мінімальне |
| Свіжість даних | Завжди свіжі | Завжди свіжі | На момент збірки |
| Коли використовувати | Дашборди, кабінети | Сторінки з динамічними даними | Блоги, документація |
Next.js дозволяє комбінувати стратегії: одна сторінка -- SSG, інша -- SSR, третя -- CSR. Обирай стратегію для кожної сторінки окремо!
Pages Router: getStaticProps (SSG)
Функції getStaticProps та getServerSideProps належать до Pages Router (директорія pages/). У сучасному App Router дані отримують інакше -- ми розглянемо це в уроці 9.6. Але розуміти Pages Router важливо, бо він все ще широко використовується.
getStaticProps виконується під час збірки і передає дані в компонент:
// pages/blog.tsx (Pages Router)
import type { InferGetStaticPropsType, GetStaticProps } from "next";
interface Post {
id: number;
title: string;
body: string;
}
export const getStaticProps: GetStaticProps<{ posts: Post[] }> = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=10");
const posts: Post[] = await res.json();
return {
props: {
posts,
},
};
};
export default function BlogPage({
posts,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<div>
<h1>Блог</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body.slice(0, 100)}...</p>
</li>
))}
</ul>
</div>
);
}
Що відбувається:
- Під час
npm run buildNext.js викликаєgetStaticProps - Функція отримує дані з API
- Дані передаються як
propsу компонент - Next.js генерує статичний HTML з цими даними
getStaticProps виконується тільки на сервері, під час збірки. Код всередині неї ніколи не потрапляє в браузерний бандл. Тут можна безпечно звертатися до бази даних або використовувати серверні секрети.
Pages Router: getServerSideProps (SSR)
getServerSideProps виконується на кожен запит:
// pages/dashboard.tsx (Pages Router)
import type { InferGetServerSidePropsType, GetServerSideProps } from "next";
interface DashboardData {
user: string;
notifications: number;
lastLogin: string;
}
export const getServerSideProps: GetServerSideProps<{
data: DashboardData;
}> = async (context) => {
// context містить req, res, query, params...
const { req } = context;
// Перевірка авторизації
const token = req.cookies.token;
if (!token) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}
// Отримання даних
const data: DashboardData = {
user: "Олексій",
notifications: 5,
lastLogin: new Date().toISOString(),
};
return {
props: { data },
};
};
export default function DashboardPage({
data,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<div>
<h1>Привіт, {data.user}!</h1>
<p>Сповіщень: {data.notifications}</p>
<p>Останній вхід: {data.lastLogin}</p>
</div>
);
}
Ключові відмінності від getStaticProps:
- Виконується на кожен запит (не під час збірки)
- Має доступ до
context(request, response, cookies, query params) - Може робити redirect
- Повільніше, бо чекає на сервер
ISR -- Incremental Static Regeneration
Золота середина між SSG і SSR. Сторінка генерується статично, але оновлюється через заданий інтервал:
// pages/news.tsx (Pages Router)
export const getStaticProps: GetStaticProps = async () => {
const res = await fetch("https://api.example.com/news");
const news = await res.json();
return {
props: { news },
revalidate: 60, // Оновлювати кожні 60 секунд
};
};
Як працює ISR:
- Перший запит -- повертає заздалегідь згенеровану сторінку
- Через 60 секунд Next.js у фоні перегенеровує сторінку
- Наступний запит отримує оновлену версію
ISR -- ідеальний вибір для контенту, який оновлюється періодично: новини, товари в магазині, рейтинги. Ти отримуєш швидкість SSG з актуальністю, близькою до SSR.
Коли що обирати?
| Тип контенту | Стратегія | Чому |
|---|---|---|
| Блог, документація | SSG | Контент змінюється рідко |
| Новини, каталог товарів | SSG + ISR | Контент оновлюється, але не щосекунди |
| Дашборд, профіль | SSR | Потрібні актуальні персональні дані |
| Чат, real-time дані | CSR | Дані оновлюються постійно на клієнті |
| Лендінг, маркетинг | SSG | Статичний контент, максимальна швидкість |
Практичний приклад: блог зі статтями
// pages/blog/index.tsx (Pages Router)
import Link from "next/link";
import type { GetStaticProps, InferGetStaticPropsType } from "next";
interface Post {
id: number;
title: string;
body: string;
}
export const getStaticProps: GetStaticProps<{ posts: Post[] }> = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
const posts: Post[] = await res.json();
return {
props: { posts },
revalidate: 3600, // Оновлювати раз на годину
};
};
export default function BlogPage({
posts,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<div>
<h1>Блог</h1>
{posts.map((post) => (
<article key={post.id}>
<Link href={`/blog/${post.id}`}>
<h2>{post.title}</h2>
</Link>
<p>{post.body.slice(0, 150)}...</p>
</article>
))}
</div>
);
}
Підсумок
- CSR -- рендеринг на клієнті (стандартний React), погано для SEO
- SSR (
getServerSideProps) -- рендеринг на сервері при кожному запиті, актуальні дані - SSG (
getStaticProps) -- генерація HTML під час збірки, максимальна швидкість - ISR -- SSG з періодичним оновленням (
revalidate) - Next.js дозволяє комбінувати стратегії на рівні окремих сторінок
Що далі?
У наступному уроці вивчимо dynamic routes з getStaticPaths -- як створювати окремі SSG-сторінки для кожної статті блогу, товару, або користувача.
Корисні посилання:
- Next.js: Data Fetching -- офіційна документація Pages Router
- Next.js: ISR -- Incremental Static Regeneration
- Patterns: SSR vs SSG -- порівняння патернів рендерингу