Есть большой класс сайтов, где содержимое меняется не по действию пользователя, а когда автор сел и написал новый текст: лендинги, документация, блоги, справочные разделы. Для них держать работающий сервер, который на каждый запрос собирает страницу заново, — трата без отдачи: результат будет одинаковым для всех и до следующей правки текста.
Такие сайты собирают заранее: во время сборки страницы превращаются в готовые 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: серверный рендер и серверные компоненты — что делать, когда страница зависит от пользователя.
- Окружение и секреты во фронтенде — тот же приём проверки на входе, только для настроек.
- Производительность фронтенда — что ещё влияет на скорость первого экрана.