Mobile PSI 44 → 100 на legacy MODX: пошаговый гайд
- PSI
- Core Web Vitals
- MODX
- оптимизация
- lazy-load
PSI Mobile 44 — это потеря половины мобильного трафика на этапе загрузки. На сайте ozsm.ru (legacy MODX с 2018 года, B2B вибропогружатели) подняли PSI Mobile с 44 до 100, Accessibility с 84 до 100, Best Practices до 96, SEO до 100. Все Core Web Vitals в зелёной зоне. Без переписывания сайта, на старом MODX, за десять дней. Ниже — реальные цифры по каждому фиксу.
Контекст
Стартовое состояние ozsm.ru на 23 апреля 2026:
- PSI Mobile Performance: 44, Desktop: 68
- FCP Mobile: 8,8 с / LCP Mobile: 11,3 с / TBT: 1 108 мс / CLS: 0,001
- Accessibility: 84 (WCAG нарушения)
- Bundle JS: ~520 КБ — две копии jQuery, html5shiv, dead gtag, Я.Чат скрипты
- Reverse-CSP: отсутствует
- Reka-капча: Google reCAPTCHA на формах = трансграничная передача ПД
Состояние через 10 дней:
| Метрика | Было | Стало |
|---|---|---|
| PSI Mobile Performance | 44 | 100 |
| PSI Desktop Performance | 68 | 100 |
| PSI Accessibility | 84 | 100 |
| FCP Mobile | 8,8 с | 1,1 с |
| LCP Mobile | 11,3 с | 1,9 с |
| TBT | 1 108 мс | 0 мс |
| CLS | 0,001 | 0,000 |
| jQuery bootup | 2,4 с | 0,6 с (-75%) |
| Bundle JS | ~520 КБ | ~50 КБ (gzip) |
Каждый патч — отдельный коммит с замером до/после. Ниже разбираем критические.
B11.3: lazy Я.Карты через IntersectionObserver
Виджет Яндекс.Карт = ~250 КБ JS + 200 КБ CSS на странице, где карта показывается. Если она ниже первого экрана — её можно вообще не грузить, пока юзер не подскроллит.
<!-- На странице — placeholder без подключения api-maps.yandex.ru -->
<div id="ymap-placeholder"
style="height: 400px; background: #1a1a1a;"
data-coords="53.2415,50.2212"
data-zoom="14"></div>
const mapEl = document.getElementById('ymap-placeholder');
if (mapEl) {
const observer = new IntersectionObserver((entries) => {
if (!entries[0].isIntersecting) return;
observer.disconnect();
const s = document.createElement('script');
s.src = 'https://api-maps.yandex.ru/2.1/?lang=ru_RU&apikey=YOUR_KEY';
s.onload = () => ymaps.ready(initMap);
document.head.appendChild(s);
}, { rootMargin: '200px' });
observer.observe(mapEl);
}
rootMargin: '200px' — карта начинает загружаться за экран до того как появится в viewport, так что пользователь не увидит «пустого места».
Эффект: 250 КБ JS откладываются в 30 строк vanilla.
B11.3c: Я.Метрика lazy через requestIdleCallback
Стандартный Я.Метрика-снippet грузит mc.yandex.ru/metrika/tag.js сразу при загрузке страницы. Это +120 КБ JS до первого взаимодействия.
Lazy-инициализация обёртывает init в requestIdleCallback({timeout:3000}) плюс триггеры на первое взаимодействие плюс 7-секундный fallback-таймер. Защита от Lighthouse simulation тоже здесь — Lighthouse не двигает мышью и не скроллит, поэтому без 7-секундного fallback Метрика просто не успевает инициализироваться в замере.
// /public/metrika-init.js
(function() {
const COUNTER_ID = window.__YA_COUNTER_ID__;
if (!COUNTER_ID) return;
let initted = false;
function init() {
if (initted) return;
initted = true;
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],
k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window,document,"script","https://mc.yandex.ru/metrika/tag.js","ym");
window.ym(COUNTER_ID, "init", {
clickmap: false, // -50 КБ
webvisor: false, // -200 КБ
accurateTrackBounce: true,
trackHash: true,
trackLinks: true,
});
}
function schedule() {
if (window.requestIdleCallback) {
window.requestIdleCallback(init, { timeout: 3000 });
} else {
setTimeout(init, 1000);
}
}
['scroll', 'click', 'touchstart', 'keydown'].forEach(e => {
document.addEventListener(e, schedule, { once: true, passive: true });
});
setTimeout(schedule, 7000);
})();
Эффект: TBT минус ~300 мс, bundle JS минус ~250 КБ.
Critical CSS inline без preload+onload
Очень популярный совет — «загрузите CSS асинхронно через <link rel=preload> + onload=this.rel='stylesheet'». На большом legacy-сайте без полного critical CSS этот паттерн даёт CLS 0,443 (Poor zone) — догружающийся bundle меняет шрифты, отступы, размеры.
Лучше: ~1.5 КБ Critical CSS прямо в <head>, render-blocking, и потом основной bundle уже не render-blocking. Размер бандла перестаёт играть роль для FCP/LCP.
На MODX это сделали через chunk: один chunk с минифицированным CSS только для above-the-fold (header, hero, основная типографика). Размер — 1,3 КБ gzip.
Дедупликация JS и удаление мёртвого кода
После аудита bundle нашли:
- Две копии jQuery — одна в
<head>, вторая черезdocument.writeизyandex.st(легаси-RevSlider). Убрали document.write, оставили одну. - html5shiv — старый polyfill для IE6-8, который никто уже не поддерживает. Удалён.
- dead gtag — Google Analytics tag, оставшийся после миграции на Я.Метрику два года назад. Удалён.
- Я.Чат — отключён, но скрипт всё равно подгружался. Удалён.
Подключили minifyJs=1 в MODX-конфигурации — оставшийся JS сжат и минифицирован.
Эффект: jQuery bootup 2,4 с → 0,6 с (-75%).
WebP + lazy на каталоге и продуктах
MODX-плагин на лету оборачивает все <img> в <picture> с <source srcset="...webp"> плюс loading="lazy" для всех картинок ниже первого экрана.
Размер каталога с 200+ карточек — 6 МБ JPG → 1,8 МБ WebP. Lazy-загрузка не грузит то, до чего юзер не доскроллил. На каталоге PSI Mobile 49 → 99.
A11y фиксы для +16 пунктов
Пять конкретных правок, которые подняли Accessibility с 84 до 100:
<meta name="viewport">— убралиuser-scalable=no(запрет зума — нарушение WCAG)<main>landmark — обернули контент в<main>, а не в<div>aria-hidden="true"на декоративном swiper — слайдер с фотографиями не должен читаться скрин-ридером- Color-contrast WCAG AA — поправили цвета кнопок «cookie consent» (было 3,2:1, стало 4,8:1)
- Target-size WCAG 2.5.5 — все интерактивные элементы 44×44 минимум. Где визуально хочется меньше —
border: 12px solid transparentвокруг 20×20 кнопки (хитрейка: hit-area 44×44, визуально 20×20)
Что мы из этого вынесли
- PSI 100 на legacy достижимо за 10 дней. Не нужно переписывать сайт. Lazy-load + critical CSS + дедуп JS + WebP — это 80% подъёма.
- Lazy Я.Метрика — лучший компромисс. Аналитика остаётся, влияние на Core Web Vitals — близко к нулю.
- A11y даёт «бесплатные» очки. Пять правок, день работы, +16 пунктов.
Ссылки
- PageSpeed Insights — основной инструмент замера
- WCAG 2.1 Quick Reference — критерии accessibility
- web.dev — Optimize INP — INP заменил FID с 2024
- MDN — IntersectionObserver — паттерн lazy-загрузки
- MDN — requestIdleCallback — для отложенной инициализации