Связано с Testlify

Testlify - миграция маркетингового сайта на Next.js

Возглавил архитектурное решение заменить WordPress на кеш-ориентированную архитектуру Next.js 16 для сайта из 11 000+ страниц, проверив постранично соответствие продакшену, чтобы сохранить существующий SEO-авторитет.

Маркетинговый сайт Testlify насчитывает более 11 000 страниц. Прежняя конфигурация на WordPress создавала накладные расходы на серверный рендеринг и узкие места из-за плагинов при каждом запросе, что влияло на скорость загрузки страниц и позиции в поисковых системах. Я возглавил архитектурное решение мигрировать сайт на Next.js 16, и развёртывание сейчас продолжается.

Архитектурное решение

  • Кеш-ориентированный подход вместо WordPress: страницы рендерятся один раз при первом запросе и кешируются бессрочно, вместо обращения к базе данных при каждом запросе или многочасовой пакетной пересборки для 11 000+ страниц. Вебхук Payload CMS сбрасывает кеш только для тех страниц, которые реально затронуты изменением контента.
  • Интеграция с headless CMS: контент управляется через Payload CMS, самостоятельно размещаемую headless CMS на базе Postgres, позволяя маркетинговым командам редактировать и публиковать страницы, пока хуки инвалидации кеша автоматически подхватывают каждое изменение.
  • Миграция в масштабе: спланирована и выполнена поэтапная миграция 11 000+ страниц, проверенная постранично против живой карты сайта продакшена, с сохранением структуры URL, канонических тегов и существующего SEO-авторитета по всем категориям.

Почему это важно

Перевод сайта из 11 000+ страниц с WordPress на кеш-ориентированную архитектуру сокращает время загрузки страниц, улучшает техническую SEO-производительность и позволяет маркетинговым командам публиковать обновления без привязки к инженерным деплоям.

Ответственность, работа всё ещё продолжается

Я возглавил архитектурное решение уйти с WordPress. Это не сольная разработка: более 2 500 коммитов примерно от 15-20 участников с июля 2026 года, активность сохраняется вплоть до этого месяца, а на момент последней проверки статуса единственным оставшимся шагом был перевод DNS на продакшен.

Почему это не классический Jamstack

Очевидный подход для 11 000+ страниц: статическая генерация во время сборки. Он здесь не подходит: полная пересборка по всем локалям и типам контента заняла бы часы, а одно изменение контента не должно требовать пересборки всего сайта, чтобы выйти в продакшен. Вместо этого каждый динамический маршрут (category, author, hr-tools, hr-guide, blog, test-library и другие) следует одной и той же схеме: сборка предварительно рендерит ровно одну образцовую страницу на тип маршрута, поскольку Cache Components требует хотя бы один параметр для сборки, а сама сборка должна выполняться без единого запроса к базе данных. Каждая другая страница под этим маршрутом рендерится при первом реальном запросе под Cache Components в Next.js 16, а затем остаётся кешированной бессрочно (cacheLife установлен в собственную константу Next INFINITE_CACHE), пока хук afterChange в Payload CMS не вызовет подписанный эндпоинт ревалидации для этой конкретной страницы. Изменения контента распространяются за секунды, а не за полный цикл пересборки, при этом кеш со стороны посетителя по-прежнему ведёт себя как edge-кеш CDN. Сам эндпоинт ревалидации подписан HMAC и проверяется сравнением за постоянное время, и намеренно ограничен инвалидацией только одного тега за раз: только отдельный, вручную запускаемый административный путь может очистить тег для всей коллекции, так что утёкший секрет вебхука может в худшем случае инвалидировать одну страницу, а не обрушить кеш всего сайта разом.

Два бага фреймворка, найденные в продакшене и исправленные у источника

Во время сборки возникли две отдельные проблемы Next.js 16, обе теперь задокументированы прямо в next.config.ts, чтобы следующий человек не открывал их заново на своём опыте. Во-первых, cacheComponents по умолчанию незаметно включает enablePrerenderSourceMaps, что убивало продакшен-сборку по памяти во время статической генерации страниц; проблема была прослежена до исходного кода самого Next и исправлена явным переопределением. Во-вторых, использование Infinity для времени жизни кеша сериализуется в JSON как null на пути к воркерам сборки Next, что незаметно понижает этот маршрут до некешируемого вместо выдачи ошибки, ломая пререндеринг во всех локалях, пока это не будет обнаружено; исправлено использованием собственной константы Next INFINITE_CACHE вместо сырого Infinity.

Исправление SEO для граничного случая стриминга

При стриминге в Cache Components автоматический notFound() в Next не может зафиксировать HTTP-статус 404 до того, как оболочка страницы уже начала стримиться клиенту, поэтому краулер, читающий только эту оболочку, никогда его не увидит. Исправление вручную вставляет директиву noindex в первый флаш, чтобы краулер, остановившийся на оболочке, всё равно получил правильный сигнал ещё до того, как остальная часть страницы разрешится.

Модель данных и масштаб

  • 11 000+ страниц формируются из нескольких типов контента (category, author, hr-tools, hr-guide, leadership, test-library), пересекающихся с каждой поддерживаемой локалью через next-intl, а не из одного плоского источника контента.
  • Каждый продакшен-URL WordPress проверяется против вывода этого проекта в отдельном аудите (docs/audits/url-parity-with-production.md): URL, который сегодня отдаёт WordPress, а этот проект возвращает 404, это потерянный трафик, а не приемлемый пробел.
  • Юнит-тесты (Vitest) покрывают генерацию карты сайта, откат к сид-данным каталога и ротацию связанного контента; Playwright покрывает сквозные сценарии.

Build vs. Buy

Самостоятельно размещаемая Payload CMS на Postgres вместо SaaS headless CMS: сохраняет владение контентом и базой данных внутри компании, вместо оплаты SaaS-подписки за место для маркетингового сайта. Собственные Cache Components и инвалидация по тегам Next.js вместо самописного слоя CDN/ISR: поведение кеширования обеспечивается фреймворком, а не инфраструктурой, которую этой команде иначе пришлось бы строить и эксплуатировать самой.

Стек

Next.js 16, React, TypeScript, Payload CMS на Postgres, next-intl для локализации, Vitest для юнит-тестов, Playwright для сквозных тестов, развёрнуто на Vercel.