← назад к разделу

Есть большой класс сайтов, где содержимое меняется не по действию пользователя, а когда автор сел и написал новый текст: лендинги, документация, блоги, справочные разделы. Для них держать работающий сервер, который на каждый запрос собирает страницу заново, — трата без отдачи: результат будет одинаковым для всех и до следующей правки текста.

Такие сайты собирают заранее: во время сборки страницы превращаются в готовые HTML-файлы, а дальше их просто раздают.

Три способа получить страницу

Разница между подходами — в том, когда выполняется код, который собирает разметку.

подходкогда собирается страницачто нужно в бою
рендер в браузереу пользователя, после загрузки скриптараздача файлов
серверный рендерна сервере, на каждый запросработающий сервер
статическая сборкаодин раз при сборке проектараздача файлов

У статики два следствия, и оба приятные. Первое: отдавать готовый файл умеет что угодно — обычный веб-сервер, CDN, объектное хранилище; масштабирование бесплатно, падать нечему. Второе: страница приходит мгновенно, ей не нужно ни ждать сервер, ни выполнять скрипт.

Ограничение ровно одно, и оно жёсткое: во время сборки уже должно быть известно, что показывать. Персональная выдача, корзина, данные пользователя — не сюда.

Как это выглядит на практике

В Next статическая сборка включается настройкой экспорта:

// next.config.ts
export default {
  output: "export",
};

После next build в папке сборки лежат готовые файлы: index.html, about/index.html и так далее. Их можно положить куда угодно — сервер приложений больше не нужен.

Если у страниц есть параметр в адресе, список значений нужно объявить заранее — сборщик должен знать, сколько файлов создавать:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await readAllPosts();          // читаем файлы контента
  return posts.map((post) => ({ slug: post.slug }));
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await readPost(params.slug);
  return <article dangerouslySetInnerHTML={{ __html: post.html }} />;
}

Интерактив при этом никуда не девается: React в браузере работает как обычно — меню, поиск по странице, переключатель темы, форма подписки на внешний сервис. Нельзя только того, что требует своего сервера при рендере.

Контент в MDX

Хранить тексты прямо в JSX неудобно: автор правит верстку вместо текста, а любая опечатка ломает сборку. Поэтому контент выносят в файлы Markdown. MDX — это Markdown, в который можно вставлять компоненты:

---
title: "Как мы ускорили каталог"
date: 2026-08-14
tags: [производительность, кеш]
cover: /images/catalog.png
---

Каталог открывался четыре секунды. Разбираем, куда уходило время.

<Callout type="warning">
  Замеры сняты на реальном трафике, а не на localhost.
</Callout>

## Что показал профиль

Сверху — фронтматтер: поля статьи в YAML. Снизу — обычный текст, в котором при необходимости стоят компоненты.

Почему поля нужно проверять на сборке

Фронтматтер — это данные, которые пишет человек руками. Значит, в них будут опечатки: пропущенная дата, tags строкой вместо списка, картинка, которой нет. Если не проверять, ошибка проявится в бою — пустой заголовок в поисковой выдаче или упавшая страница.

Проверка на сборке решает это одним шагом: описываем схему и разбираем ею каждый файл.

import { z } from "zod";

const postSchema = z.object({
  title: z.string().min(1).max(80),
  date: z.coerce.date(),
  tags: z.array(z.string()).default([]),
  cover: z.string().startsWith("/images/").optional(),
});

export function parsePost(file: string, raw: unknown) {
  const result = postSchema.safeParse(raw);
  if (!result.success) {
    throw new Error(`${file}: ${result.error.issues.map((i) => i.path + " " + i.message).join("; ")}`);
  }
  return result.data;
}

Дальше это ставят шагом сборки — и сломанный текст не доезжает до сайта, а падает у автора с внятным сообщением. Тот же приём, что и с переменными окружения: проверять на входе, а не надеяться.

Что ещё проверяют у контентного сайта

У сайта, который живёт с поиска, есть набор вещей, которые ломаются молча. Их дешевле проверять автоматически, чем замечать через месяц по просевшему трафику:

  • Заголовок и описание есть у каждой страницы и укладываются в разумную длину.
  • Внутренние ссылки ведут на существующие страницы: переименовали файл — нашлись все, кто на него ссылался.
  • У картинок есть размеры и альтернативный текст — иначе страница прыгает при загрузке, а часть читателей не понимает, что на изображении.
  • Карта сайта и канонические адреса совпадают с тем, что реально опубликовано.

Каждая проверка — обычный скрипт, который читает файлы и падает с ошибкой. Ставится в сборку рядом с линтером.

Когда статики перестаёт хватать

Момент, когда нужно уходить на серверный рендер, определяется одним вопросом: зависит ли содержимое страницы от того, кто её открыл.

  • Цены и наличие меняются раз в час, одинаковые для всех — статика с пересборкой по расписанию.
  • Личный кабинет, корзина, персональные рекомендации — сервер.
  • Поиск по каталогу с фильтрами — зависит от объёма: сотни товаров можно отдать файлом и фильтровать в браузере, десятки тысяч — уже нет.

Промежуточный вариант — собрать страницу заранее, но обновлять её по мере устаревания, не пересобирая сайт целиком. В Next это делается настройкой времени жизни страницы; из статического экспорта такой вариант выпадает, для него нужен работающий сервер.

Коротко

  • Статическая сборка превращает страницы в готовые HTML-файлы во время сборки проекта; в бою нужна только раздача файлов.
  • Ограничение одно: содержимое должно быть известно заранее и одинаково для всех.
  • Интерактив в статике работает: React в браузере никуда не делся, нельзя только своего сервера при рендере.
  • Страницы с параметром в адресе требуют явного списка значений на сборке.
  • Контент удобно держать в MDX: Markdown с возможностью вставить компонент, поля статьи — во фронтматтере.
  • Фронтматтер пишет человек, поэтому его разбирают схемой и падают на сборке, а не в бою.
  • Проверки заголовков, ссылок и картинок — обычные скрипты в сборке; они ловят то, что иначе замечают по просевшему трафику.
  • Как только содержимое зависит от того, кто смотрит, — статика заканчивается и нужен сервер.

Что почитать дальше

  • Next.js: серверный рендер и серверные компоненты — что делать, когда страница зависит от пользователя.
  • Окружение и секреты во фронтенде — тот же приём проверки на входе, только для настроек.
  • Производительность фронтенда — что ещё влияет на скорость первого экрана.