Что было
Сайт, который вы сейчас читаете, долго жил как полноценное серверное приложение. Next.js с серверным рендерингом, PostgreSQL через Prisma, Redis, очереди и воркеры, Docker Compose с несколькими контейнерами. Всё это было нужно не блогу, а сервису онлайн-аудита: краулер обходил сайт клиента, снимал скриншоты, считал метрики, складывал отчёт в личный кабинет, принимал оплату. Плюс авторизация, админка, поиск по сайту на Meilisearch и программные страницы по городам.
Проблема в том, что контентная часть — блог, услуги, кейсы, калькулятор — тащила за собой всю эту инфраструктуру. Чтобы поправить опечатку в статье, нужно было пересобрать контейнер и убедиться, что база, Redis и воркеры живы. Любое обновление зависимостей превращалось в проверку десятка движущихся частей.
Что стало
Я разделил проект. Всё, что требует сервера, ушло из этого репозитория. Осталась чистая контентная часть, и она собирается в статику.
Технически это Next.js 16 с App Router и одной строкой в конфиге — output: export. Плюс trailingSlash, чтобы каждый маршрут был папкой с index.html, отключённая оптимизация изображений (её некому выполнять без сервера) и убранный заголовок X-Powered-By. React 19, TypeScript, Tailwind 4.
Контент — не в базе, а в коде. Каждая статья блога — TypeScript-модуль с метаданными и markdown-текстом. При сборке markdown разбирается в блоки (абзацы, заголовки, списки, таблицы), и из них рендерятся страницы. Услуги, кейсы, навигация устроены так же. Аналитика — Яндекс Метрика, вставленная в корневой layout. Никаких переменных окружения, никаких секретов.
Сборка выдаёт папку out с готовым сайтом: HTML, ассеты, sitemap.xml и robots.txt. На сегодня это 867 статических страниц, из них 748 — статьи блога с пагинацией по 18 на страницу и четырьмя категориями.
Как устроен деплой
Хостинг — Timeweb Cloud Apps, тип приложения «Фронтенд». Настроек ровно три: команда сборки npm run build, каталог результата out, Node.js 20. Приложение привязано к ветке репозитория, и каждый push запускает сборку. Дальше содержимое out раздаётся через CDN.
Я специально измерил, сколько проходит от коммита до продакшена. Последний коммит в ветку — 11:19 UTC, заголовок Last-Modified у файлов на сайте — 11:22 UTC. Три минуты. Сборка 867 страниц идёт в 11 потоков и укладывается в это время вместе с выкладкой.
Маршрутизация работает из коробки: адрес без слэша на конце получает 308-редирект на версию со слэшем, папка отдаёт свой index.html. Никаких правил переписывания, никакого сервера приложений.
Что я выиграл
- Нечему падать. Нет базы — нет проблем с коннектами, миграциями и бэкапами. Нет процесса Node на сервере — нет утечек памяти и перезапусков в три ночи.
- Безопасность. Августовский security-релиз Next.js закрывал две критические уязвимости с удалённым выполнением кода на сервере. Статический сайт этим не задеть: сервера, который выполнял бы код, просто нет. Обновиться всё равно стоит, но не в панике.
- Скорость. HTML лежит на CDN и отдаётся с ближайшего узла. Время до первого байта не зависит от того, проснулась ли база.
- Воспроизводимость. Любой коммит можно собрать локально и получить байт в байт тот же сайт. Откат — это откат коммита.
- Стоимость. Тариф статического фронтенда против VPS с Postgres, Redis и воркерами — разница в разы.
Чем заплатил
Честно про ограничения, потому что статика подходит не всем.
- Нет серверной логики. Формы, оплата, личный кабинет, поиск с ранжированием — всё это либо внешние сервисы, либо отдельное приложение. Поиска по блогу сейчас нет; Meilisearch ушёл вместе с сервером.
- Время сборки растёт с контентом. 867 страниц собираются за минуты, но каждая новая сотня статей добавляет время. Здесь помогает дисковый кэш Turbopack из Next.js 16.3 — повторные сборки читают неизменившиеся страницы из кэша.
- Никакой персонализации на сервере. Всё, что зависит от пользователя, делается на клиенте.
- Программные страницы по городам пришлось убрать: сотни почти одинаковых страниц, генерируемых по шаблону, — это то, за что и Яндекс, и я сам не хотим отвечать в статике.
Грабли после запуска: мягкая 404
Самое интересное нашлось уже на работающем сайте. Я проверил несуществующий адрес — и получил главную страницу с кодом 200. Хостинг настроен так, что любой неизвестный путь падает на index.html, как у SPA. При этом в папке out лежит готовый 404.html, который Next.js генерирует сам.
Почему это плохо: поисковик, зайдя по битой или выдуманной ссылке, получает «настоящую» страницу с кодом 200 и может её проиндексировать. Яндекс Вебмастер называет это «мягкими 404» и считает ошибкой. Плюс любой мусорный URL с внешнего сайта начинает выглядеть как дубль главной.
Лечение — на стороне хостинга: указать, что для неизвестных путей нужно отдавать 404.html с кодом 404, а не index.html с 200. Проверить просто: запросить заведомо несуществующий адрес и посмотреть код ответа. Если 200 — у вас та же история. Об этом стоит помнить всем, кто выкладывает статику на «фронтенд»-хостинги: настройка SPA-fallback там часто включена по умолчанию.
Кому подходит статика
Мой критерий простой. Если ответ «да» на все три вопроса — экспортируйте в статику и не сомневайтесь:
- Контент меняется руками, а не пользователями? Блог, услуги, кейсы, лендинги, документация — да. Форум, маркетплейс, личный кабинет — нет.
- Все интерактивные штуки можно вынести во внешние сервисы или отдельное приложение? Формы, чат, оплата — обычно да.
- Готовы принять, что «опубликовать» означает «пересобрать»? С деплоем за три минуты — да.
Если хотя бы одно «нет» — нужен сервер, и это нормально. Но для большинства сайтов услуг и корпоративных сайтов статика — самый дешёвый и самый надёжный вариант из существующих. Как я делаю такие проекты — на странице корпоративных сайтов на Next.js. А если у вас сейчас WordPress или Битрикс и хочется понять, стоит ли переезжать, начните с разбора какую CMS выбрать.
