
Миграция монолита без смены URL: как сохранить привычный интерфейс
Сотни старых страниц можно перевести на новый стек без массовых редиректов и резкой смены интерфейса. Для этого маршрут остаётся прежним, а решение о том, какой сервис отдать пользователю, принимает инфраструктура. Такой подход особенно полезен командам, чей сайт уже индексируется, содержит сложные пользовательские сценарии и не может остановиться ради большой миграции.
В одном из разборов переезда двадцатилетнего Perl-монолита команда оставила прежние адреса, постепенно подключила Nuxt-каталог и вынесла общие элементы в единый источник. Эта последовательность важнее выбора конкретного фреймворка: она позволяет менять архитектуру частями, сохраняя для посетителя знакомый путь по сайту.
Страница — не единственная единица миграции
У старого сайта на Perl и Template Toolkit были разные части пользовательского пути: витрина услуг, мастер заказа и личный кабинет. Первые две динамичные части начали переносить раньше. Новая версия появлялась для одной услуги, а бэкенд направлял пользователя в старый или новый мастер в зависимости от готовности этого сценария.
Такой порядок снижает размер риска. Команда проверяет один маршрут, собирает обратную связь и только затем добавляет следующий. Пока функциональность нового кабинета была неполной, пользователю оставили доступ к прежнему варианту. Момент отключения старой части наступает после переноса сценариев, а не по календарной дате.
Витрина долго могла оставаться на легаси-стеке: статичным лендингам хватало существующих возможностей. Причина для нового этапа появилась, когда витрина стала каталогом. Страницы товаров требовалось создавать через административную панель, собирать из управляемых блоков и связывать с уже существующими компонентами. Появилась и потребность в серверном рендеринге для поиска.
Прежний URL становится контрактом
Перенос готовых адресов на новые URL кажется простым техническим решением, но для давно работающего сайта он затрагивает индексацию, закладки, внешние ссылки и ожидания посетителей. В описанном случае сотни страниц уже находились в поиске, поэтому адреса решили сохранить. Сохранили и визуальную преемственность: человек не должен замечать смену приложения, переходя между соседними разделами.
Первым практичным механизмом стала маршрутизация в Nginx Ingress Controller. В конфигурации перечисляли URL, которые уже реализованы в Nuxt. Запрос по такому пути уходил в новое приложение, остальные обслуживал легаси-монолит. Новую страницу можно добавить отдельной строкой, а при проблеме быстро вернуть на прежний обработчик.
Ручной список маршрутов не выглядит окончательной архитектурой, однако на старте он даёт команде две ценные вещи: прозрачность и обратимость. Его стоит выбирать, когда объём перехода растёт постепенно, список страниц известен, а настройка сложной автоматизации задержит запуск. Когда адресов становится слишком много, сам список показывает, что пора пересмотреть способ управления маршрутами.
Дублирование интерфейса имеет короткий срок годности
На первом этапе шапку и футер сверстали отдельно в старом и новом приложениях. Это помогло быстро запустить каталог, но породило две версии одинаковых элементов. Пока менялись редкие ссылки или небольшие детали, стоимость такой синхронизации оставалась приемлемой.
Ситуация изменилась с ребрендингом и требованием редактировать меню через админку. Ежедневно повторять правки в двух кодовых базах — источник расхождений: одна ссылка может исчезнуть, порядок пунктов может стать разным, а визуальная ошибка окажется только на части страниц. Здесь миграция упирается уже в поддержку интерфейса, а не в выбор маршрута.
Полезная проверка перед переносом звучит так: какие компоненты пользователь видит при переходе между старой и новой страницами? Если элемент должен выглядеть и вести себя одинаково, ему нужен единый источник разметки, данных или правил сборки. Для шапки и футера это обычно важнее, чем для содержимого конкретной страницы.
Шапка как HTTP-потребитель
В рассмотренной реализации компонент шапки живёт в Nuxt и используется в его обычном layout. Тот же компонент доступен легаси-приложению по HTTP. Для этого сделали две Vite-сборки: клиентскую и серверную. Серверный entry point создаёт Vue-приложение через createSSRApp и рендерит его функцией renderToString в HTML-строку.
После сборки нужны два вида артефактов. В manifest.json лежат пути к JavaScript и CSS шапки, а серверный entry point экспортирует функцию рендеринга. Обработчик /header собирает JSON с headHtml для стилей и скрипта и appHtml с готовой разметкой. Старый шаблон получает этот ответ и подставляет оба фрагмента в свой layout.
Схема избавляет от параллельной вёрстки: правка компонента в Nuxt попадает и на новые страницы, и в старое приложение. При этом легаси остаётся самостоятельным потребителем с понятной точкой интеграции. Обновление общего элемента можно тестировать на обеих сторонах до публикации.
Что проверить до следующей волны страниц
Поэтапная миграция требует проверок на стыках приложений. Для каждого нового URL полезно подтвердить, что Ingress направляет его в нужный сервис, серверный рендеринг возвращает ожидаемый HTML, а стили и клиентский JavaScript загружаются по путям из manifest. Отдельно стоит пройти навигацию между старой и новой страницами: в ней чаще всего проявляется отличие общих компонентов.
Нужно заранее определить владельца списка маршрутов и правила отката. В исходном примере маршрутизацию поддерживали фронтенд-разработчики без отдельной DevOps-команды, поэтому простая конфигурация соответствовала возможностям команды. В другой организации этот слой может принадлежать платформенной команде, но порядок действий должен оставаться понятным тем, кто выпускает страницы.
Старый стек можно заменять годами, если он продолжает корректно обслуживать нужные сценарии. Успех такого переезда определяется не количеством переписанных файлов, а сохранностью пользовательского пути: прежние адреса отвечают, общий интерфейс не расходится, а каждый перенесённый участок можно проверить и откатить отдельно.
Обсудить проект
Остались вопросы? Напишите нам
Оставьте имя и телефон — перезвоним в течение часа, разберём задачу и предложим решение.