Вивчай

Pre-rendering та Data Fetching

Next.js Pre-rendering — друкарський верстат що виробляє готові копії сторінок заздалегідьNext.js Pre-rendering — друкарський верстат що виробляє готові копії сторінок заздалегідь

У попередньому уроці ми створили перший Next.js проект з маршрутизацією. Але справжня сила Next.js -- у тому, як він рендерить сторінки. На відміну від звичайного React, Next.js може генерувати HTML заздалегідь. Це називається pre-rendering.


Три стратегії рендерингу

CSR vs SSR vs SSG: порівняння стратегій рендерингуCSR vs SSR vs SSG: порівняння стратегій рендерингу

CSR -- Client-Side Rendering

Це те, як працює звичайний React-додаток (Vite):

  1. Сервер відправляє порожній HTML (<div id="root"></div>)
  2. Браузер завантажує JavaScript
  3. JavaScript рендерить сторінку на стороні клієнта
  4. Користувач бачить контент

Проблема: поки JS не завантажиться -- сторінка порожня. Пошукові боти (Google) можуть не дочекатися.

SSR -- Server-Side Rendering

  1. Користувач робить запит
  2. Сервер виконує React-код і генерує готовий HTML
  3. Браузер одразу показує контент
  4. JavaScript "гідратує" сторінку (додає інтерактивність)

Плюси: контент видно одразу, хороший SEO. Мінуси: кожен запит навантажує сервер.

SSG -- Static Site Generation

  1. Під час збірки (npm run build) Next.js генерує HTML
  2. Готові HTML-файли розміщуються на CDN
  3. Кожен запит отримує заздалегідь готову сторінку

Плюси: найшвидший варіант, мінімальне навантаження. Мінуси: дані "заморожені" з моменту збірки.


Порівняння стратегій

КритерійCSRSSRSSG
Швидкість першого завантаженняПовільноШвидкоНайшвидше
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>
  );
}

Що відбувається:

  1. Під час npm run build Next.js викликає getStaticProps
  2. Функція отримує дані з API
  3. Дані передаються як props у компонент
  4. 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:

  1. Перший запит -- повертає заздалегідь згенеровану сторінку
  2. Через 60 секунд Next.js у фоні перегенеровує сторінку
  3. Наступний запит отримує оновлену версію
Порада

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-сторінки для кожної статті блогу, товару, або користувача.

Інфо

Корисні посилання: