Всем привет! Недавно мы с командой моей студии разработки panfilov.digital запустили новую версию интернет-магазина «Аквилон» (akvilon.kz) – одного из крупных игроков на рынке торговли стройматериалами и товарами для дома в Казахстане.
Пять лет назад мы разработали первую версию магазина на Nuxt 2. За годы поддержки и развития проект превратился в громоздкий монолит, с которым становилось всё сложнее работать. Для бизнеса заказчика это выражалось в конкретных проблемах: сайт стал медленнее открываться под растущей нагрузкой, а внедрение изменений в каталог или логику оформления заказа рисковало превратиться в долгие и дорогостоящие доработки.
С выходом Nuxt 3 перед нами, как и перед всеми, кто поддерживал проекты на второй версии, встала дилемма:
Попытаться «перевезти» проект на новую версию через Nuxt Bridge и постепенную миграцию.
Признать, что код свое отжил, и переписать все с нуля.
Спойлер: мы выбрали рерайт. Попытка миграции, по нашим оценкам, превратила бы наш проект в «зомби» — полуживого монстра из легаси-кода, «мостов» и костылей, поддерживать которого было бы еще дороже.

В этой статье расскажу, почему миграция с Nuxt 2 на 3 для крупных проектов — это иллюзия, как мы выстраивали новую архитектуру на Nuxt 3 и FSD, и почему последующее обновление до Nuxt 4 прошло почти безболезненно.
Почему миграция с Nuxt 2 на 3 была бы «фаталити» для нашего проекта
Когда мы начали изучать вопрос, читая опыт других команд и анализируя свой код, вывод был однозначным: для нашего монолита плавная миграция нереальна. Nuxt 2 → 3 — это не минорное обновление, это мажорный скачок, который ломает всё.
Вот несколько «фаталити», с которыми мы столкнулись бы при попытке сохранить старый код:
Фаталити №1: Composition API vs. Options API
Это не просто новый синтаксис. Это фундаментально иной подход к написанию компонентов.
Вся логика в старом проекте была построена на data, methods, computed и watch из Options API. Nuxt 3 идеологически построен вокруг <script setup> и Composition API. Нам бы всё равно пришлось переписывать практически каждый компонент, чтобы он соответствовал современным стандартам, а не выглядел как "код из 2019-го, запущенный в 2025-м".
Фаталити №2: Стейт-менеджер (Vuex vs. Pinia)
Старый проект использовал Vuex. Новая экосистема Nuxt 3 «заточена» под Pinia. Это не просто замена одного пакета на другой (как axios на ofetch), это полная переписка всей логики управления состоянием и потоков данных.
«Фаталити» №3: Роутинг и получение данных
В Nuxt 2 у нас была сложная, исторически сложившаяся структура в папке pages/ для генерации динамических маршрутов каталога.
📂 pages
├── 📂 advice
├── 📂 brands
├── 📂 c
│ └── 📂 _category
│ └── 🟢 _filter.vue
├── 📂 help
├── 📂 p
├── 📂 payment
├── 📂 personal
├── 📂 preview
├── 📂 promo
├── 🟢 _vue
└── 🟢 activate.vueСтарая реализация содержала два типа роутов: /c/elektroinstrumenty/ (категория) и /c/elektroinstrumenty/brands-husqvarna/ (фильтр), и оба они «сидели» на одном динамическом файле _filter.vue.
В новой реализации мы хотели строгого разделения:
_catalog⇒sectionCode(например,/c/elektroinstrumenty/)_filter⇒staticFilter(например,/c/elektroinstrumenty/brands-husqvarna/)
📂 src
└── 📂 app
├── 📂 assets
├── 📂 composables
├── 📂 layouts
├── 🔧 middleware
├── 📂 nuxt-config
└── 📂 pages
├── 📂 [staticPage]
├── 📂 advice
├── 📂 brands
└── 📂 c
└── 📂 [sectionCode]
├── 🟢 [staticFilter].vue
└── 📄 index.vueПытаться натянуть старую логику extendRoutes на новую файловую систему роутинга Nuxt 3 (особенно с их подходом к [...slug].vue) было бы сложнее, чем спроектировать маршрутизацию заново. Это требовало не переноса, а полного переосмысления.
Фаталити №4: Экосистема и пакеты
Команда npm update здесь не спасет. Почти все ключевые UI-библиотеки (например, Swiper Slider) для Nuxt 3 вышли как новые major-версии или были заменены альтернативами. У них кардинально другой API. Нам пришлось бы выкинуть старые реализации слайдеров, модалок, форм валидации и написать их с нуля под новые пакеты.
Строим «правильно» с нуля: Nuxt 3 + FSD + Symfony
Чистый лист дал нам шанс построить архитектуру, которая не развалится через год.
Фундамент: Feature-Sliced Design (FSD)
Мы сразу решили строить проект по методологии FSD. Это позволило разложить сложный e-commerce по понятным «полочкам»: shared, entities, features, widgets, pages.
Это дало нам строгие правила импорта и четкие границы ответственности. Связка с бэкендом на Symfony тоже стала прозрачной: сущность ProductOption на бэке — это entities/product-option на фронте.
Командная работа: Дизайн, Бэк и Фронт в одной упряжке
1. Дизайнер, который смотрит в API
Наш дизайнер Макс перед тем, как рисовать макеты, изучал структуры данных от бэкенда.
Пример: Раньше у нас было два последовательных модальных окна: «Данные покупателя» и «Данные получателя». Изучив API и сценарии, мы объединили их в одно, улучшив UX и упростив логику фронтенда.
2. «Чистый» контракт с бэкендом (Symfony)
Мы договорились о «гигиене» данных. Бэкенд гарантирует предсказуемые типы.
Например, бэк отдает пустую строку "" вместо string | null или data: { a: null } вместо отсутствующего ключа. Это невероятно упростило проверки на фронте — никаких бесконечных if (data && data.a).
Всю сложную бизнес-логику (расчеты цен, скидок, доступных вариантов доставки) мы также жестко зафиксировали на бэкенде. Фронтенд только отображает готовое.
Сила Composition API и паттерн «Фабрика»
Мы выносили всю сложную логику в composables. Самым показательным стал кейс с оформлением заказа, где мы использовали паттерн «Фабрика» для управления тремя типами доставки.
Структура получилась такой:
useOrderBase.ts: содержит общие данные (покупатель, состав корзины).useOrderDelivery.ts: расширяет базу логикой для курьерской доставки.useOrderPickup.ts: расширяет базу логикой для самовывоза.
📂 composables
├── 📂 api-data-fetch
├── 📂 common-catalog
└── 📂 create-order
└── 📂 factories
├── 📂 retail
│ ├── 🟦 base.ts
│ ├── 🟦 delivery.ts
│ └── 🟦 pickup.ts
└── 📂 wholesale
├── 🟦 base.ts
├── 🟦 delivery.ts
└── 🟦 pickup.tsРазделяй и властвуй: Auth vs Unauth
Мы следовали принципу «один компонент — одна задача» и избегали «компонентов-богов».
На примере виджета «Сравнение товаров»:
<template>
<AuthCompareWidget v-if="isLoggedIn" />
<UnauthCompareWidget v-else />
</template>
AuthCompareWidget.vue работает с API и базой данных, а UnauthCompareWidget.vue — только с localStorage. Это позволило избежать каши из if-else внутри одного файла.
«Легкий апгрейд»: почему переход с Nuxt 3 на 4 прошел гладко
А теперь — вишенка на торте. Недавно мы обновили наш новый проект с Nuxt 3 до Nuxt 4. И знаете что? Это было несложно.
Контраст с ужасом миграции «2 → 3» был колоссальным. Nuxt 4 стал строже, требуя явного указания ключей в useAsyncData и useFetch, а также предложил новую структуру директории app/.
Почему для нас это прошло гладко?
Мы были готовы. Мы изначально взяли за правило прописывать уникальные
keyдля всехuseAsyncData(чтобы избежать гидратационных ошибок), и это сработало на опережение.FSD-архитектура. Наша структура проекта уже была модульной. Перенос папок под новые стандарты
app/стал простой механической задачейgit mv, а не рефакторингом логики.
Это и есть главная выгода рерайта: правильная архитектура, заложенная на старте, окупается при первом же мажорном обновлении фреймворка.
Что касается метрик производительности сайта (Core Web Vitals): мы еще в процессе их тюнинга. Поскольку приоритетом был полный перезапуск бизнеса с обновленным UX для удержания клиентов, мы не гнались за «зеленой зоной» на старте любой ценой.
Однако главный эффект от перехода на Nuxt 3 мы ощутили сразу — это резкий рост Time-to-Market. Благодаря современной архитектуре новой версии фреймворка (мгновенная сборка на Vite, нативная типизация) мы перестали тратить время на борьбу с инструментами. Разработка стала предсказуемой, доставка новых фич — быстрее, работа сайта — стабильнее.
Итоги: Главные уроки
Рерайт — это не провал, а стратегия. Для легаси-проектов это часто единственное верное решение, которое экономит бюджет в долгосрочной перспективе, отсекая накопленный годами техдолг.
Миграция 2 → 3 — это иллюзия. Для крупного проекта это равносильно рерайту, только гораздо больнее, дороже и с высоким риском создать «зомби-проект» на костылях.
Composition API меняет мышление, а не только синтаксис. Переход сделал разработку более гибкой. Фронтенд фактически сместился в сторону функционального стиля: вместо жесткой структуры Options API мы теперь пишем чистые, переиспользуемые функции (composables).
FSD + Nuxt 3 + TypeScript — это мощная связка для e-commerce. Она дает структуру и масштабируемость, которые не разваливаются при росте команды и функционала.
Культура кода окупается сразу. Наш внутренний стайлгайд, привычка к строгой типизации и явным ключам сделали последующее обновление до Nuxt 4 простой механической задачей, а не очередным авралом.
Надеемся, наш опыт поможет кому-то принять правильное решение: мучиться с миграцией или резать по живому и строить заново.
Готовы ответить на вопросы про FSD и нюансы перезда на Nuxt 3 / 4 в комментариях!