Главная
Все проекты

Аэромир

Корпоративный сайт для инжиниринговой компании климатических систем

Роль

Fullstack-разработчик (фронтенд + серверный BFF-слой)

Год

2026

Стек

Nuxt 4 / Vue 3 / TypeScriptNuxt UI v4GSAP 3 (MotionPathPlugin)vee-validate + ZodWordPress REST API (headless CMS, ACF, Yoast SEO)Nitro (BFF)

Аэромир

Задача

Компании нужен был сайт, который решает две задачи одновременно:

  1. Продающая витрина реализованных объектов и услуг с быстрой, современной фронтенд-частью
  2. Контент (статьи, цены, объекты), который маркетинг-команда может редактировать сама, без разработчика на каждую правку. Отсюда — решение развести проект на headless WordPress (контент и медиа) и Nuxt 4 (рендеринг, интерактив, SEO), не отдавая при этом браузеру ни WP-креды, ни сырую структуру REST-ответов.

Этапы

01

Архитектура и карта сайта

  • Спроектировал структуру страниц: услуги, объекты с фильтрацией по типу, SEO-лендинги цен, статьи.
  • Заложил жесткое разделение на Nuxt-приложение (app/) и Nitro-сервер (server/) с самого начала, чтобы WordPress выступал только изолированным источником данных, а не точкой прямого обращения из браузера.
02

Разработка BFF-слоя поверх WordPress REST

  • Поднял серверные роуты, проксирующие WP REST API через Basic Auth (Application Passwords).
  • Написал типизированные трансформеры, которые конвертируют сырые ответы WordPress (с вложенными _embedded-медиа и ACF-полями) в плоские, чистые TypeScript-модели (Project, Article).
03

Компонентная система и доступность

  • Перенёс Figma-макет в переиспользуемые Vue-компоненты. Настроил Tailwind v4 в CSS-first режиме (@theme в main.css).
  • Собрал форму заявки на vee-validate + Zod, интегрировав стандарты доступности прямо в базовый компонент UiFormField (автоматическая генерация aria-invalid и aria-describedby для ошибок).
04

Проектирование анимаций на GSAP

  • Разработал сложную секцию «Этапы работ» с использованием GSAP MotionPathPlugin.
  • Создал три независимые траектории под разные брейкпоинты (mobile/tablet/desktop). Добавил поддержку ленивой загрузки анимаций по IntersectionObserver и написал надежный resize-обработчик для плавной работы в мобильных браузерах.
05

Формы и доступность

  • Собрал форму заявки на 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 не дёргается при каждом открытии страницы:

typescript
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кб просто не попадают в бандл.
typescript
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-блока до витрины реализованных объектов и подвала с формой). В данный момент дорабатываются внутренние страницы (каталог объектов с фильтрами, статьи, услуги). Ожидаемый срок сдачи — ближайшие недели.

Результаты

9

секций на главной странице

3

уникальные SVG-траектории для адаптивной анимации этапов работ

0

учетных данных WP или сырых REST-ответов, уходящих в браузер (абсолютная изоляция данных через BFF).

Экраны