Аэромир
Корпоративный сайт для инжиниринговой компании климатических систем
Роль
Fullstack-разработчик (фронтенд + серверный BFF-слой)
Год
2026
Стек
Nuxt 4 / Vue 3 / TypeScriptNuxt UI v4GSAP 3 (MotionPathPlugin)vee-validate + ZodWordPress REST API (headless CMS, ACF, Yoast SEO)Nitro (BFF)

Задача
Компании нужен был сайт, который решает две задачи одновременно:
- Продающая витрина реализованных объектов и услуг с быстрой, современной фронтенд-частью
- Контент (статьи, цены, объекты), который маркетинг-команда может редактировать сама, без разработчика на каждую правку. Отсюда — решение развести проект на headless WordPress (контент и медиа) и Nuxt 4 (рендеринг, интерактив, SEO), не отдавая при этом браузеру ни WP-креды, ни сырую структуру REST-ответов.
Этапы
Архитектура и карта сайта
- Спроектировал структуру страниц: услуги, объекты с фильтрацией по типу, SEO-лендинги цен, статьи.
- Заложил жесткое разделение на Nuxt-приложение (
app/) и Nitro-сервер (server/) с самого начала, чтобы WordPress выступал только изолированным источником данных, а не точкой прямого обращения из браузера.
Разработка BFF-слоя поверх WordPress REST
- Поднял серверные роуты, проксирующие WP REST API через Basic Auth (Application Passwords).
- Написал типизированные трансформеры, которые конвертируют сырые ответы WordPress (с вложенными
_embedded-медиа и ACF-полями) в плоские, чистые TypeScript-модели (Project,Article).
Компонентная система и доступность
- Перенёс Figma-макет в переиспользуемые Vue-компоненты. Настроил Tailwind v4 в CSS-first режиме (
@themeвmain.css). - Собрал форму заявки на
vee-validate+ Zod, интегрировав стандарты доступности прямо в базовый компонентUiFormField(автоматическая генерацияaria-invalidиaria-describedbyдля ошибок).
Проектирование анимаций на GSAP
- Разработал сложную секцию «Этапы работ» с использованием GSAP MotionPathPlugin.
- Создал три независимые траектории под разные брейкпоинты (mobile/tablet/desktop). Добавил поддержку ленивой загрузки анимаций по IntersectionObserver и написал надежный resize-обработчик для плавной работы в мобильных браузерах.
Формы и доступность
- Собрал форму заявки на vee-validate + Zod с типизированной схемой валидации (имя, телефон по количеству цифр, необязательный email по регулярке).
- Модалка вызывается программно через
useOverlay()из Nuxt UI, что позволяет переиспользовать один компонент формы с разным контекстным текстом (CTA в «Этапах работ» и персональные CTA у карточек команды).
Общий UiFormField сразу даёт aria-invalid, aria-describedby и role="alert" для ошибок — доступность не как отдельная задача, а как побочный эффект переиспользуемого компонента.
Процесс
Технологический стек
| Слой | Технологии |
|---|---|
| Фронтенд | Nuxt 4, Vue 3, TypeScript, Tailwind CSS v4, Nuxt UI v4 |
| Анимации | GSAP 3 (включая MotionPathPlugin) |
| Формы и валидация | vee-validate + Zod |
| CMS (Backend) | WordPress REST API (headless, ACF, Yoast SEO) |
| BFF-слой | Nitro (серверные роуты, cachedEventHandler) |
О проекте и задаче
«Аэро-мир» — новосибирская компания полного цикла по климатическим системам (на рынке с 2011 года).
Бизнесу требовалось решить две конфликтующие задачи: получить современную, быструю, продающую витрину с хорошим SEO и при этом оставить маркетологам привычную админку для самостоятельного редактирования статей, цен и портфолио. Отсюда архитектурное решение: разделить проект на Headless WordPress (база данных и медиа) и Nuxt 4 (рендеринг, интерактив, SEO). Я разрабатываю сайт с нуля — от проектирования BFF-слоя до верстки и сложных анимаций.
Под капотом
BFF-слой поверх WordPress REST API
Главное правило проекта — браузер никогда не общается с WordPress напрямую. Я разделил логику на Nuxt-приложение (app/) и Nitro-сервер (server/).
Серверные роуты Nitro выступают как BFF (Backend-for-Frontend): они проксируют запросы через Basic Auth, запрашивают у CMS только нужные поля (через _fields и _embed), чтобы не тащить на клиент лишний JSON, и отдают фронтенду чистые данные.
Написан слой типизированных трансформеров: сырые данные WP (поля ACF, вложенные медиа, Yoast SEO) на лету превращаются в строгие TypeScript-модели (Project, Article). Фронтенду не нужно гадать, есть ли в объекте нужное поле — он работает с готовыми интерфейсами.
Для оптимизации я завернул списки объектов в cachedEventHandler, а разрешение slug категории в ID закэшировал в памяти процесса. Теперь WordPress не дёргается при каждом открытии страницы:
const categoryCache = new Map<string, number>();
export async function getCategoryId(slug: string): Promise<number> {
if (categoryCache.has(slug)) return categoryCache.get(slug)!;
const categories = await wpFetch<WPTerm[]>('/categories', { query: { slug } });
if (!categories[0]) throw createError({ statusCode: 500, message: `Category not found` });
categoryCache.set(slug, categories[0].id);
return categories[0].id;
}
Анимация как инженерная задача
Секция «Этапы работ» — это сложная сцена на базе GSAP MotionPathPlugin, где маскот движется по кривой. Чтобы это работало идеально на любых устройствах:
- Отрисованы три отдельные SVG-траектории (под мобильный, планшет и десктоп), а не одна общая с CSS-трансформациями. Точки прогресса синхронизированы индивидуально под каждый брейкпоинт.
- Реализован хитрый resize-хендлер. В мобильном Safari при скролле скрывается адресная строка, что вызывает событие
resizeи дёргает анимацию. Я написал обработчик, который перезапускает GSAP только если реально изменилась ширина экрана или layout-режим, игнорируя прыжки высоты. - GSAP и его плагины грузятся через динамический
import()и только по IntersectionObserver. На страницах без анимации эти ~30кб просто не попадают в бандл.
let resizeRaf = 0;
const onResize = () => {
if (resizeRaf) cancelAnimationFrame(resizeRaf);
resizeRaf = requestAnimationFrame(() => {
const nextWidth = window.innerWidth;
const nextLayoutMode = getLayoutMode();
// Игнорируем изменение только высоты вьюпорта (хак для iOS Safari)
if (nextWidth === lastViewportWidth && nextLayoutMode === lastLayoutMode) return;
lastViewportWidth = nextWidth;
lastLayoutMode = nextLayoutMode;
initAnimation();
});
};
Кастомные UI-решения
Написан composable useHeader, который блокирует скролл при открытии мобильного меню без визуального «прыжка» контента (компенсируя ширину скроллбара через padding-right). Бесконечные ленты партнеров сделаны на чистом CSS с фолбеком для prefers-reduced-motion. Формы собраны на vee-validate + Zod, а доступность (aria-invalid, role="alert") заложена прямо на уровне базовых UI-компонентов.
Текущий статус и результаты
Сайт находится в активной разработке. Полностью готова логика BFF-слоя, а также 9 секций главной страницы (от hero-блока до витрины реализованных объектов и подвала с формой). В данный момент дорабатываются внутренние страницы (каталог объектов с фильтрами, статьи, услуги). Ожидаемый срок сдачи — ближайшие недели.
Результаты
секций на главной странице
уникальные SVG-траектории для адаптивной анимации этапов работ
учетных данных WP или сырых REST-ответов, уходящих в браузер (абсолютная изоляция данных через BFF).