ИИ-акселератор сайтов на Rust: прокси, который переписывает HTML на лету
- Rust
- edge
- Core Web Vitals
- GEO
- llms.txt
- accessibility
- PageSpeed
- MODX
Весной мы вручную подняли мобильный PageSpeed сайта на MODX с 44 до 100 — это заняло десять рабочих дней (кейс ОЗСМ, ссылка внизу). Осенью взяли копию того же сайта в состоянии 2017–2024 года, до любых оптимизаций, поставили перед ней прокси на Rust и за два дня получили 99. Ни одной строки в коде сайта. Ниже — что это за прокси, из каких фильтров состоит и на какие грабли наступили.
Цифры
Главная страница демо-стенда, медианы шести проб PageSpeed Insights, второй и последующие прогоны:
| Показатель | Как было | Через акселератор |
|---|---|---|
| PageSpeed, телефон | 52 | 99 |
| PageSpeed, компьютер | 54 | 100 |
| LCP, телефон | 15,4 с | 1,9 с |
| FCP | 7,0 с | 0,9 с |
| TBT | 244 мс | 0 мс |
| CLS | 0,060 | 0,002 |
| Lighthouse Accessibility | 78 | 100 |
llms.txt | нет | 53 страницы |
| BreadcrumbList в разметке | нет | 52 из 53 страниц |
| Накладные расходы рерайтера | — | ≈ 1,6 мс на страницу 300 КБ |
| Строк кода сайта изменено | — | 0 |
Ряд роста мобильного балла по этапам: 52 → 66 → 76 → 84 → 84 → 98 → 99. Скачок с 84 на 98 дал один шаг — убрали preload шрифта из <head>: с ним PSI выдаёт «две моды», потому что Lantern ставит шрифт в критический путь FCP. Компьютер: 54 → 90 → 91 → 99 → 100.
Цепочка
браузер → Traefik → Angie proxy_cache → Rust-рерайтер (hyper + lol-html) → origin клиента
└→ /_img/* → imgproxy (AVIF/WebP)
- Traefik — TLS, rate-limit, проверка ботов по обратному DNS.
- Angie
proxy_cache— кеш готовых страниц. Сжимаем один раз при записи в кеш: brotli «на лету» в Traefik резал отдачу из кеша в пять раз. - Рерайтер — HTTP-сервер на hyper 1 и потоковая перезапись через lol-html 3. Страница не буферизуется: первые байты уходят клиенту, пока хвост ещё идёт от origin.
- imgproxy — картинки в AVIF/WebP по запросу; на прототипе AVIF отрезал 72 % веса JPEG.
Один клиент = один YAML с секциями filters, geo, a11y, theme, fixes. Секции нет — фильтр выключен. Откат — флаг passthrough: прокси остаётся, но отдаёт origin как есть.
Фильтры скорости
lcp_src_attrs/lcp_preload— находим картинку LCP в data-атрибутах слайдера и добавляем<link rel="preload">в<head>. Первый ответ с ним не кешируется: адрес картинки зависит от страницы.css_async— стили ниже сгиба грузятся асинхронно; critical CSS лежит файлом клиента и собирается офлайн.delay_scripts+delay_inline_contains— третьи стороны (карты, счётчики, чаты) стартуют по первому взаимодействию или в idle.lcp_poster— статичный постер первого слайда до инициализации revslider плюс резерв высоты без jQuery. Это и дало CLS 0,002.inline_after_paint— инициализация слайдера после first-contentful-paint.script_preload— для того, что всё же нужно рано.font_preload: []по умолчанию. См. грабли выше.
GEO: слой для ИИ-ботов
Модуль geo делает сайт читаемым для ИИ-поиска без правки контента:
llms.txtиllms-full.txtсобираются из sitemap origin (обход раз в сутки, один запрос в секунду).- robots-менеджер: секции для ИИ-ботов — поисковых пускаем, «обучающих» отключаем по выбору клиента; добавляем
Clean-param. - Текстовая версия любой страницы в Markdown: по расширению
.mdили по заголовкуAccept: text/markdown. - BreadcrumbList и FAQPage извлекаются из DOM и добавляются как JSON-LD.
- IndexNow по хэшу страницы; до перевода DNS работает в режиме dry-run.
- Отчёт по ИИ-ботам с проверкой обратного DNS, чтобы отличать настоящих от подделок.
Никакой генерации текста моделью: всё, что попадает в машиночитаемый слой, взято со страницы. Про честность отдельно: видимость в ответах нейросетей измерима только после перевода DNS, первые данные приходят через 4–8 недель, а Алиса берёт источники из топ-20 Яндекса — место в её ответах никто гарантировать не может.
Доступность и тема
Модуль a11y чинит то, что Lighthouse и axe-core находят на чужом сайте: viewport без user-scalable=no, role="main", contrast-fix.css из отчёта axe-core (собирается офлайн), label_fix для чужих виджетов. Результат — Accessibility 78 → 100.
Тема — статический экспорт Dark Reader (около трёх минут на режим). CSS темы грузится только по выбору пользователя, поэтому Lighthouse меряет прежнюю светлую страницу и скорость не падает. Капсула справа вверху — не больше 1 КБ inline без внешнего JS: ☀/☾ мгновенно, по ▾ — контраст, шрифт A/A+/A++, картинки, сброс. Выбор хранится в localStorage, иначе берётся prefers-color-scheme. Кнопка «Наверх» ставится по тому же образцу, что и на нашем сайте, родная кнопка сайта скрывается.
Грабли
- Тело POST. Проксируем chunked — Apache + php-fpm видят пустой
$_POST. Буферизуем и отдаём сContent-Length. Нашлось кликом по фильтру каталога, не тестами. - Хост в JSON. Подмена origin-хоста нужна и в AJAX-ответах, не только в HTML.
- Cookie-баннер. Чужой обработчик кнопки был сломан ещё на origin; чиним на краю.
- Preload шрифта в
<head>даёт две моды PSI. Убрали — 84 → 98. - Brotli на лету в Traefik режет HIT кеша в пять раз. Сжимать при записи в кеш.
- Первая проба PSI врёт. Медиана ≥ 6 проб со второго прогона, уникальный параметр в URL на каждую пробу.
Как это делалось
Два дня, пятнадцать заданий, тридцать два PR. Облачная сессия Claude писала код фильтров и документ передачи по каждому заданию, локальная выкатывала на стенд и снимала медианы PSI. Дефекты, которые не ловились тестами (пустой $_POST, хост в JSON), находились кликами по живому сайту. Это отдельная тема для статьи о рабочем методе.
Что дальше
Ближайшие шаги: проверка сторонних скриптов на соответствие 152-ФЗ, ежемесячный отчёт «до/после», отдача из кеша при падении хостинга, замена reCAPTCHA на SmartCaptcha на краю, RUM с реальных устройств. Всё это — фильтры того же прокси, сайт клиента по-прежнему не трогаем.