Что было до
22 мая 2026 переехали с avat-studio.ru на avat.studio. Аудит в тот же день нашёл 23 страницы и пачку дыр в мете. 11 title длиннее 60 знаков, 5 description длиннее 165, 22 страницы без twitter:card. og:image лежал в SVG, а Telegram и VK такие превью не рисуют вообще. Домен ават.рф числился в whois, но зона была пуста: ссылки на него вели в никуда. Мобильный PageSpeed показывал 33 при TBT 19,6 секунды.
Дальше сайт вырос до 93 страниц на двух языках. Шапку и футер правили руками в каждом файле, и они разъезжались между разделами. Отдельно болела страница кейсов. На ней лежало 29 карточек плоской сеткой, а скрипт «показать ещё», написанный для главной, прятал всё начиная с пятой. Кнопки, которая вернула бы остальные, на /cases/ не существовало. Клиент видел 4 работы из 29.
Как устроена сборка
Сайт остался статическим HTML, который сервер отдаёт файлами. Фреймворка нет, package.json нет, node_modules нет. Вся сборка это 20 питон-скриптов в корне репозитория и build.py как единая точка входа.
Шапка, мега-меню и мобильная шторка описаны один раз внутри _apply_unified_nav.py. Оттуда они раскладываются по 51 странице из словаря PAGES. Карта задаёт для каждой страницы активный пункт меню, наличие якорей #pricing и #contact и язык. Страница, которой в словаре нет, пропускается с сообщением SKIP: молча испортиться она не может. Скрипты идемпотентны, второй прогон печатает unchanged.
- build.py раскладывает шапку и футер, поднимает версию ассетов и гоняет 4 проверки: битые внутренние ссылки, валидность каждого блока JSON-LD, длина title и description, сверка sitemap.xml с диском в обе стороны. Есть режим --check, который ничего не меняет
- Версия ассетов живёт в одной точке: сейчас ?v=111 на 346 ссылках. build.py смотрит git diff по CSS и JS и поднимает номер сам, когда файлы менялись
- Языковой переключатель считает пару RU/EN прямо из пути файла. Для страниц без перевода есть карта EN_FALLBACK, чтобы флаг не уводил посетителя на 404
- В футере блог-поста стоят 2-3 релевантные услуги вместо всех восьми: одинаковый набор ссылок на 14 страницах подряд Яндекс читает как спам-паттерн, поэтому вариативность зашита в генератор
Агенты, которые собирают кейсы, обязаны приложить к каждому числу путь до файла или номер коммита. Однажды копирайтер отказался переносить в текст цифру с лендинга: замером в коде она не подтверждалась, и он написал об этом прямо. Правило задумывалось как защита от выдуманных чисел. Работает оно шире: как проверка собственного маркетинга.
Страница кейсов
49 карточек разложены по 6 смысловым группам, у каждой свой подзаголовок в духе «зачем вам эта пачка». Поверх групп работает фильтр из 6 кнопок, который режет поперёк группировки. Категория карточки живёт в data-cat, группа в data-group, и одна карточка может попасть сразу в две категории. Фильтр гасит лишнее, пересчитывает каждую группу и целиком прячет ту, где не осталось видимых карточек. Иначе над пустотой торчали бы заголовки.
Архивная группа свёрнута, но при активном фильтре раскрывается сама. Работает глубокая ссылка: /cases/#automation открывает нужный фильтр при загрузке. Кнопку «Показать N кейсов» дорисовывает скрипт, и если JS отключён, архив остаётся раскрытым и все 49 карточек видны сразу. Коммерческие предложения лежат там обезличенными образцами с условными суммами: смотреть в них надо на структуру и подачу.
Посадочные страницы под кейсы
Под кейсы написан генератор. 24 страницы выпущены тремя партиями по восемь за два дня, 28 и 29 августа 2026. По каждому кейсу отрабатывала цепочка из трёх агентов: сбор фактуры из репозитория, потом маркетолог с копирайтером, потом SEO. На партию из восьми страниц это 24 агента.
Скелет у всех один: «Что происходило до нас», «Что сделали», «Как это работает внутри», «Где споткнулись», «Что получилось», «Частые вопросы». На страницу идут три блока микроразметки: Article, BreadcrumbList и FAQPage с пятью парами вопрос-ответ. Хлебные крошки видимые, не только в разметке. Секция «Где споткнулись» обязательна: без неё кейс читается рекламой.
Где споткнулись
Правку, которую не сделал скрипт, вы сделали не везде. Скорость чинили на главной и радовались результату. В это время 57 внутренних страниц тянули Google Fonts с CDN, хотя те же Inter и Unbounded уже лежали локально. Чистый дубль и render-blocking: внутренние показывали FCP и LCP 3,6 секунды против 1,1 на главной.
- CLS искали в variable-шрифтах, откатывали, пробовали. Замер на холодном кэше нашёл виновника: display=swap подменял системный шрифт на Unbounded, заголовок подрастал и двигал весь первый экран. Одно слово optional дало 0,871 → 0,002
- На бою CLS был около 1, локально 0,002. На localhost отложенный styles.css приходил мгновенно, на медленной сети приходил поздно. Вылечили тем, что весь CSS главной уехал инлайном в head
- При сабсеттинге шрифтов скачали диапазон U+0460-052F: это редкая кириллица, файл 1,8 КБ почти пустой. Основную А-Я пропустили, и заголовок тихо падал на системный шрифт. После правильного диапазона Unbounded вырос до 30 КБ
- GSAP ScrollTrigger съедал около 21 секунды процессорного времени на телефоне ради эффектов появления. Заменили на scroll-слушатель с throttle через requestAnimationFrame, сами анимации перенесли в CSS
- PageSpeed показывал 38,9 секунды в графе Other на главном потоке. Это писал сессии вебвизор Яндекс.Метрики, при том что весь наш JS занимал 483 мс. Вебвизор выключили, визиты и цели оставили
- Окно скриншота в карточке брало высоту от ширины экрана, а ширину от колонки сетки. Совпасть они не могли, и object-fit: cover срезал кадр до трети. Помогла жёсткая пропорция 2:1 и contain
Что получилось
Мобильный PageSpeed вырос с 33 до 70, десктопный держит 95. В трекере на 17.07.2026 было 153 запроса: 28 в ТОП-3, 95 в ТОП-4-10, средняя позиция 7,8. По запросу «вайбкодинг студия» сайт стоял первым. Ниша низкочастотная, поэтому позиции здесь стоит читать как срез на дату замера. Оценка в майском аудите за один день работы поднялась с 8,7 до 9,3 из 10. В репозитории 748 коммитов, деплой идёт через git push, сервер забирает выкладку сам.
Частые вопросы
Можно ли обойтись без фреймворка на сайте в 93 страницы?
Да. Сайт avat.studio это 93 HTML-страницы: 72 русских и 21 английская. Ни package.json, ни node_modules в репозитории нет, всю сборку делают 20 питон-скриптов и build.py как единая точка входа. Деплой идёт через git push, сервер забирает выкладку сам.
Как править шапку сразу на всех страницах?
Шапка, мега-меню и мобильная шторка описаны один раз внутри _apply_unified_nav.py. Скрипт раскладывает их по 51 странице из словаря PAGES, где для каждой указаны активный пункт меню, язык и наличие якорей. Страницу, которой в словаре нет, скрипт пропускает с сообщением SKIP, поэтому испортиться молча она не может.
Что сильнее всего тормозило сайт на телефоне?
Два тяжёлых куска. GSAP ScrollTrigger забирал около 21 секунды процессорного времени ради эффектов появления: его заменили на scroll-слушатель с requestAnimationFrame, а сами анимации перенесли в CSS. Ещё 38,9 секунды в графе Other писал вебвизор Яндекс.Метрики, при том что весь собственный JS занимал 483 мс. Вебвизор выключили, визиты и цели оставили.
Из-за чего вёрстка прыгает при загрузке?
Виноват был шрифт. Параметр display=swap подменял системный шрифт на Unbounded уже после отрисовки, заголовок подрастал и двигал весь первый экран. Замена swap на optional дала CLS 0,871 → 0,002. Отдельно нашли ошибку сабсеттинга: скачали диапазон U+0460-052F, файл на 1,8 КБ без основной кириллицы, после правильного диапазона Unbounded вырос до 30 КБ.
Что проверяется при каждой сборке?
Четыре проверки на каждом прогоне build.py: битые внутренние ссылки, валидность каждого блока JSON-LD, длина title и description, сверка sitemap.xml с диском в обе стороны. Там же поднимается версия ассетов, сейчас это ?v=111 на 346 ссылках, номер растёт сам по git diff файлов CSS и JS. Есть режим --check, который ничего не меняет и только показывает ошибки.
