Как настроить пререндеринг React-сайта без Next.js
Пререндеринг React-сайта без Next.js — это процесс, при котором приложение сначала собирается, затем запускается в браузерном окружении для каждого публичного маршрута, а полученный HTML сохраняется как готовый файл. В простом варианте pipeline использует Vite для сборки, Puppeteer для открытия маршрутов и чтения итогового HTML, а веб-сервер затем отдаёт сохранённые документы без необходимости выполнять React при каждом запросе. Такой подход подходит для заранее известного набора стабильных страниц: главной, услуг, статей, контактов и других публичных URL. Он не заменяет серверную маршрутизацию, проверку статусов, настройку canonical и контроль данных: каждый маршрут нужно включить в список, корректно отрендерить, сохранить в правильное место и проверить перед публикацией.
Что делает пререндеринг
Обычная клиентская сборка React может передать браузеру HTML-шаблон и JavaScript, после чего приложение сформирует содержание страницы на клиенте. При пререндеринге тот же JavaScript запускается заранее в автоматизированном браузере. Браузер открывает конкретный URL, ждёт загрузки приложения и получает итоговый DOM. Затем этот HTML записывается в файл, который можно раздать через статический хостинг или Nginx.
Пререндеринг не является полноценным SSR. SSR формирует HTML на сервере во время каждого запроса. Пререндеринг создаёт HTML заранее, поэтому серверу не нужно выполнять React для уже подготовленного маршрута. Это уменьшает требования к серверу, но делает актуальность страницы зависимой от следующей сборки и публикации.
У пререндеринга есть последовательность: исходники и данные проходят производственную сборку, система получает список публичных маршрутов, браузер открывает каждый URL, приложение загружает данные, HTML сохраняется в каталог публикации, а затем результат проверяется до деплоя.
Когда метод подходит
Пререндеринг подходит для страниц, которые можно перечислить во время сборки. Это стабильные страницы сайта услуг, статьи блога, контакты, описания услуг и другие публичные документы, содержание которых не зависит от конкретного пользователя.
Метод удобен, когда проект уже создан на Vite и React, но переход на Next.js или другой фреймворк не входит в задачу. Существующую клиентскую архитектуру можно сохранить, добавив скрипт генерации HTML и правила публикации.
Пререндеринг применяют, если нужно отдавать поисковому роботу готовый текст страницы, уменьшить зависимость от выполнения JavaScript при первом просмотре, публиковать стабильные маршруты как статические документы и проверять итоговый HTML до деплоя.
Метод не устраняет ошибки в URL, данных или серверной конфигурации. Если список маршрутов неполный, часть страниц не будет создана. Если сервер для любого неизвестного адреса возвращает одну и ту же главную страницу, пререндеринг существующих страниц не устраняет soft 404.
Архитектура pipeline
Минимальный pipeline состоит из сборщика, источника маршрутов, браузера, сохранения файлов и валидатора. Сборщик создаёт производственные JavaScript-, CSS- и другие ресурсы. Источник маршрутов формирует полный список URL. Puppeteer запускает браузер и открывает страницы. Сохранение переносит полученный HTML в каталог публикации. Валидатор проверяет результат.
Список маршрутов, sitemap, внутренние ссылки и проверка HTML должны использовать один источник данных. Нельзя вручную поддерживать несколько независимых массивов адресов: при добавлении статьи один из них может остаться без обновления.
Для блога подготовленный набор обычно включает только опубликованные записи. Черновики, записи без slug и материалы с дублирующимся slug должны обнаруживаться до запуска браузера. Для каждой записи полезно заранее получить заголовок, slug, публичный URL, описание, дату публикации, статус и canonical.
Такой объект используется при формировании маршрутов, метаданных, sitemap и проверок. Если разные этапы заново рассчитывают slug, между ссылкой, HTML-файлом и canonical может возникнуть рассинхронизация.
Список маршрутов
Список маршрутов должен содержать только адреса, которые действительно должны существовать после публикации. Для сайта услуг это главная, страницы услуг, блог, статьи и контакты. Для блога к ним могут добавляться страницы пагинации.
Маршруты нужно нормализовать до запуска Puppeteer. У сайта должен быть один протокол, один домен и одно правило завершающего слеша. Внутренние ссылки, sitemap и canonical должны использовать одинаковую форму URL.
Перед генерацией проверяются следующие условия:
- URL начинается с ожидаемого пути сайта;
- в адресе нет пробелов и необработанных символов;
- после нормализации нет повторов;
- slug не пустой;
- маршрут не указывает на служебную страницу;
- опубликованная статья имеет собственный адрес;
- черновик не попал в список;
- неизвестный slug не создаётся автоматически.
Если маршрут строится из заголовка, нужна одна детерминированная функция транслитерации и нормализации. После публикации лучше хранить slug отдельно: изменение заголовка не должно автоматически менять публичный адрес.
Сборка перед запуском
Пререндеринг запускается после успешной производственной сборки. Сначала Vite создаёт клиентские ресурсы, затем браузер получает приложение с этими файлами. Если начать пререндеринг до сборки или указать неправильный каталог, браузер может открыть пустой контейнер, хотя сам процесс не завершится исключением.
Перед запуском проверяются завершение сборки, наличие каталога результата, загрузка JavaScript и CSS, соответствие base фактическому адресу, доступность данных статей и отсутствие ошибок импорта.
Если приложение запускается локально на отдельном порту, адрес должен задаваться в одном месте. Сервер предпросмотра, Puppeteer и валидатор должны использовать один параметр окружения или одну конфигурацию. Нельзя зашивать случайные порты в нескольких скриптах.
Сервер разработки и сервер просмотра производственной сборки не являются производственной инфраструктурой. Команда vite preview предназначена для проверки собранного результата. На сервере нужен веб-сервер или платформа, которая обслуживает опубликованные файлы.
Запуск Puppeteer
Puppeteer запускает браузер, создаёт страницу и открывает URL. Для каждого маршрута нужно задать viewport, выполнить навигацию, дождаться готовности приложения, проверить ошибки, получить HTML и закрыть страницу.
Ожидание только загрузки документа не всегда означает, что данные статьи уже появились. Если приложение получает материал отдельным запросом, нужно ждать конкретного признака готовности: элемента статьи, специального атрибута или завершения загрузки данных. Фиксированная задержка менее надёжна, потому что скорость окружения меняется.
При навигации нужно фиксировать фактический URL после редиректов, ответ основного документа, ошибки JavaScript, ошибки загрузки ресурсов, наличие основного контейнера, title, H1 и canonical.
Статус ответа нужно читать отдельно. Браузер может продолжить работу после ответа с ошибкой, поэтому сам факт успешного завершения навигационной команды не доказывает, что страница существует.
Ожидание содержания
Главная ошибка пререндеринга — сохранить HTML слишком рано. Браузер открыл URL, но React ещё не получил данные, поэтому в файл попал пустой контейнер или общий шаблон.
Надёжный признак готовности должен соответствовать приложению. Например, после загрузки текста и метаданных приложение может добавить специальный атрибут к корневому элементу страницы. Можно ожидать и конкретный селектор основного материала, если он присутствует на каждой публичной странице.
Появление контейнера нужно проверять вместе с содержанием. Валидатор должен убедиться, что заголовок страницы непустой, H1 соответствует маршруту, основной текст содержит осмысленный фрагмент, canonical совпадает с нормализованным URL, а экран ошибки не появился вместо статьи.
Если статья загружается из API, необходимо обрабатывать ошибку запроса. Пустой ответ не должен считаться успешно отрендеренной статьёй. При сбое pipeline должен завершиться с указанием конкретного маршрута.
Получение и сохранение HTML
После готовности страницы Puppeteer получает полный HTML текущего документа. Перед сохранением нужно выбрать единую структуру файлов.
Возможны варианты: каталог с index.html для URL с завершающим слешем или отдельный HTML-файл для URL без слеша. Нельзя смешивать форматы случайно. Генератор, веб-сервер, sitemap, canonical и валидатор должны использовать одну схему.
Сохранение должно создавать необходимые каталоги, но не должно безусловно удалять весь dist: там находятся JavaScript, CSS, изображения и другие ресурсы сборки. Очистку старых HTML-файлов нужно выполнять в заранее определённом порядке и только для управляемой области.
Каждый созданный файл должен быть связан с одним маршрутом. Два разных URL не должны случайно сохранять результат в один путь. Поэтому уникальность slug проверяется до запуска параллельных задач.
Метаданные и canonical
Для каждой страницы нужно проверить title, description, H1, основной текст и canonical. Метаданные должны соответствовать конкретному маршруту, а не оставаться от главной страницы или предыдущей статьи.
Canonical формируется из того же нормализованного URL, который используется во внутренних ссылках и sitemap. Если адрес статьи заканчивается слешем, canonical должен отражать это правило. Если выбран адрес без слеша, второй вариант не должен выдаваться как основной.
Нельзя добавлять canonical на несуществующую публикацию. Страница ошибки не должна маскироваться canonical на главную или другую статью. Описание должно быть редакционным и соответствовать содержанию, а не случайному техническому тексту.
Проверять нужно сохранённый HTML, который уйдёт на сервер. Проверка DOM во время работы браузера без проверки файла недостаточна.
Валидация
Валидатор должен проверять не только существование файла, но и соответствие страницы своему маршруту. Для каждого URL проверяются наличие ожидаемого HTML, корректного документа, непустого title, одного основного H1, основного текста и canonical.
Также проверяется совпадение canonical с ожидаемым URL, отсутствие страницы ошибки, наличие нужных внутренних ссылок и отсутствие необоснованного повторения canonical у разных публикаций.
Проверка текста не должна искать весь исходный Markdown одной непрерывной строкой. После преобразования в HTML текст разделяется тегами, пробелы нормализуются, а специальные символы преобразуются. Надёжнее извлечь текст из основного контейнера, нормализовать пробелы и проверить несколько обязательных фрагментов.
Ошибки должны группироваться по URL. Сообщение о количестве провалов без подробностей затрудняет диагностику. Валидатор должен показывать источник статьи, рассчитанный slug, ожидаемый путь, найденный файл и конкретное нарушенное условие.
Ошибки сборки
Критические условия должны завершать pipeline с ненулевым кодом: ошибка сборки, отсутствующий маршрут, дублирующийся slug, ошибка браузера, ошибочный статус, пустой HTML, неверный canonical или отсутствие итогового файла.
Для проблемного маршрута полезно сохранять HTML, скриншот, ошибки консоли и список сетевых запросов как артефакты GitHub Actions. Это позволяет исследовать сбой без повторения всей сборки.
Не следует исправлять сбой через continue-on-error, если проверка должна защищать публикацию. Такой обход скрывает ошибку, но не делает HTML корректным. Нужно определить, почему страница не создана или не прошла проверку.
Параллельная обработка
Страницы можно обрабатывать последовательно или параллельно. Последовательный режим проще диагностировать и предсказуемее расходует ресурсы, но занимает больше времени. Параллельный режим сокращает длительность сборки, однако увеличивает нагрузку на память и процессор.
Число одновременно открытых страниц нужно ограничивать. Нет необходимости создавать отдельный браузер для каждого маршрута. Обычно достаточно одного браузера и ограниченного числа вкладок, которые закрываются после обработки.
При параллельной записи каждый маршрут должен иметь собственный путь. Проверка уникальности адресов выполняется до запуска задач.
Сценарий для блога
Markdown-файл статьи содержит заголовок, фиксированный slug, описание, дату, статус и текст. Сборщик читает записи и оставляет только опубликованные материалы. Для каждой записи строится URL блога.
Затем Vite создаёт производственную сборку, локальный сервер поднимает приложение, а Puppeteer открывает URL статьи и ждёт признак готовности. После этого HTML сохраняется в выбранную структуру. Валидатор сравнивает ожидаемый список маршрутов с реально созданными файлами.
Если статья появилась в списке блога, но отсутствует в списке пререндеринга, ошибка находится в подготовке маршрутов. Если HTML создан, но пуст, нужно проверять ожидание данных и загрузчик статьи. Если текст есть, но неверен canonical, нужно проверять нормализацию URL и источник метаданных.
Такое разделение позволяет определить этап сбоя и не исправлять весь деплой вслепую.
Когда метод не подходит
Пререндеринг не является универсальной заменой SSR. Он неудобен для страниц, зависящих от авторизованного пользователя, персональных данных, корзины, сессии или других параметров запроса.
Метод также может быть неэффективным для очень большого каталога, когда каждая публикация требует запуска браузера и увеличивает время сборки. В таких проектах рассматривают серверный рендеринг, инкрементальные обновления, кеширование или разделение страниц по приоритету.
Если содержание меняется сразу после сборки, готовый HTML может устареть до следующей публикации. Пререндеринг не заменяет корректные HTTP-коды, стабильные URL, доступные ссылки, sitemap, canonical и мониторинг.
Чек-лист публикации
- Список маршрутов формируется из одного источника данных.
- Черновики и записи без slug исключаются до сборки.
- Дублирующиеся slug обнаруживаются до запуска браузера.
- Производственная сборка завершается успешно.
- Локальный сервер запускается на известном адресе.
- Puppeteer открывает полный URL каждой страницы.
- Для готовности используется содержательный признак.
- Проверяются HTTP-ответы и ошибки ресурсов.
- HTML сохраняется в едином формате.
- В каждом документе есть title, H1, основной текст и canonical.
- Canonical совпадает с нормализованным URL.
- Не сохраняется страница другой статьи или экран ошибки.
- Валидатор показывает подробную причину сбоя.
- Старые HTML-файлы не остаются после публикации.
- Результаты проверяются до передачи на сервер.
- Для ошибок сохраняются HTML и скриншоты.
Источники
- Vite: Building for Production — https://vite.dev/guide/build
- Vite: Deploying a Static Site — https://vite.dev/guide/static-deploy
- Vite: Server-Side Rendering — https://vite.dev/guide/ssr
- Vite: Command Line Interface — https://vite.dev/guide/cli
- Puppeteer: Getting Started — https://pptr.dev/guides/getting-started
- Puppeteer: Page.goto() — https://pptr.dev/api/puppeteer.page.goto
- Puppeteer: Page.content() — https://pptr.dev/api/puppeteer.page.content
- React Router: Rendering Strategies — https://reactrouter.com/start/framework/rendering
- React Router: Deploying — https://reactrouter.com/start/framework/deploying


