Мы были уверены, что сайт быстрый. Сервер отдаёт страницу за 0,4 секунды, кеш работает, картинки не по 4 мегабайта, тема писана с нуля без единого конструктора. Потом мы открыли PageSpeed Insights, переключили на «Мобильные устройства» — и увидели 58.

Самое интересное началось в списке проблем. Там не было ничего из того, на что обычно думают: ни медленного сервера, ни гигантских JPEG, ни «слишком много плагинов». Была одна строчка-фраза, которая объясняла всё:

За один рабочий день мы довели мобильные показатели до 97–100 / 100 / 100 / 100 и заодно получили 3/3 в новом блоке «Агентный веб-просмотр». Ниже — реальный код, который это сделал, что из оптимизаций не сработало, и почему последний блок скоро станет важнее остальных.
Сначала — честный дисклеймер
- Это лабораторные замеры. Lighthouse гоняет страницу на эмулированном Moto G Power с ограниченным 4G. Реальные пользователи на быстром канале увидят лучше, на дешёвом телефоне в метро — хуже.
- Показатель плавает. Тот же сайт в трёх прогонах подряд даёт 97, 99 и 100. Мы берём медиану трёх — и советуем так же делать всем, кто собирается спорить про «а у меня 89».
- 100 — не самоцель. Цель — чтобы страница открывалась и начинала слушаться пальца за секунду. Балл — лишь удобный способ проверить, что ты этого достиг.
Где прятались 40 баллов
1. Бесконечная анимация строки логотипов — 950 мс работы процессора
Лента с логотипами клиентов ехала через requestAnimationFrame: каждый кадр JavaScript считал сдвиг и писал его в transform. Выглядело идеально, стоило — почти секунду работы главного потока в первые секунды загрузки. Вот как это было:
// было: JS считает позицию каждого кадра
const tick = (now) => {
const dt = (now - last) / 1000;
last = now;
state.forEach(s => {
s.offset += s.dir * s.speed * dt;
if (s.offset <= -s.half) s.offset += s.half;
s.track.style.transform = `translate3d(${s.offset.toFixed(2)}px,0,0)`;
});
rafId = requestAnimationFrame(tick);
};
А вот то же самое, но на композиторе — главный поток вообще не участвует. Треки и так дублируются, поэтому сдвиг на -50% зацикливается бесшовно:
/* стало: анимация живёт в CSS */
@keyframes cc-marq-l { from { transform: translate3d(0,0,0) }
to { transform: translate3d(-50%,0,0) } }
.lm-track { animation: cc-marq-l var(--lm-dur, 70s) linear infinite }
.stack-row[data-dir="right"] .stack-track { animation: cc-marq-r 75s linear infinite }
@media (prefers-reduced-motion: reduce) { .lm-track, .stack-track { animation: none } }
JS остался ровно один — раз померить ширину трека и пересчитать постоянную скорость (45 px/с) в секунды анимации.
2. three.js ради одного прямоугольника
Анимированный градиент в шапке — это фрагментный шейдер на весь экран. Он тянул three.js: 1,3 МБ библиотеки (252 КБ сжатыми) ради того, чтобы нарисовать два треугольника и передать три переменные в видеокарту. Мы выбросили библиотеку и написали то же самое на «голом» WebGL:
const gl = canvas.getContext('webgl', { antialias:false, alpha:false, depth:false });
const compile = (type, src) => {
const sh = gl.createShader(type);
gl.shaderSource(sh, src); gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(sh));
return sh;
};
const program = gl.createProgram();
gl.attachShader(program, compile(gl.VERTEX_SHADER, VERT));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAG)); // тот же шейдер, что и был
gl.linkProgram(program);
gl.useProgram(program);
// один треугольник, накрывающий весь экран
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);
Вместе с циклом анимации, ресайзом и паузой за экраном — около 3 КБ кода вместо 1,3 МБ. Визуально градиент не изменился ни на пиксель.
3. Карта клиентов, которую никто ещё не видел
Секция с картой мира подгружала d3-geo, topojson и файл геометрии стран на 740 КБ — сразу на старте, хотя сама карта лежит в самом низу страницы. Теперь всё это приезжает, когда до секции остаётся 400 px:
const section = document.querySelector('.clients-map');
const io = new IntersectionObserver((entries) => {
if (entries.some(e => e.isIntersecting)) {
io.disconnect();
boot(); // только здесь грузим d3 + topojson + geojson
}
}, { rootMargin: '400px 0px' });
io.observe(section);
4. Google Fonts как третья сторона
Шрифт Manrope приезжал с fonts.googleapis.com, а сам файл — с fonts.gstatic.com: два лишних DNS-запроса и два TLS-рукопожатия на критическом пути. Забрали к себе, два подмножества вместо шести:
/* assets/css/fonts.css — свой, а не Google */
@font-face{
font-family:'Manrope'; font-style:normal; font-weight:400 800; font-display:swap;
src:url('../fonts/manrope-cyrillic.woff2') format('woff2');
unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
5. WooCommerce на страницах, где нет магазина
WooCommerce подключает свои стили и скрипты везде — на главной, в блоге, на странице «О нас». Снимаем там, где магазина нет:
function catcode_needs_woo_assets(): bool {
if (!function_exists('is_woocommerce')) return false;
if (is_woocommerce() || is_cart() || is_checkout() || is_account_page()) return true;
if (is_singular(['module', 'product'])) return true;
if (function_exists('WC') && WC()->cart && !WC()->cart->is_empty()) return true;
// страница с shortcode/блоком магазина — тоже оставляем
return false;
}
6. Картинки — да, тоже
Карточки кейсов отдавались полноразмерными JPEG на 1440 px даже на телефон. Теперь у каждого JPEG и PNG в медиатеке есть WebP-двойник, а тема сама заворачивает разметку в <picture> с фолбеком на оригинал:
add_filter('wp_get_attachment_image', function ($html) {
$srcset = catcode_webp_srcset($html); // '' если хоть для одного размера нет .webp
if ($srcset === '') return $html;
return sprintf('<picture><source type="image/webp" srcset="%s"%s>%s</picture>',
esc_attr($srcset), $sizes, $html);
});
Что получилось


Что НЕ помогло
Половина советов из типовых чеклистов в нашем случае не дала ничего — и это не менее полезно знать, чем то, что сработало.
- WebP сам по себе. Картинки на главной лежат ниже первого экрана и грузятся лениво. Они раздули вес страницы, но на балл почти не влияли — прирост пошёл от JavaScript, а не от них.
deferна всех скриптах. Он у нас уже стоял — все скрипты темы были отложены до оптимизации. Это не спасает, если отложенный скрипт потом рисует каждый кадр.- Preload «на всякий случай». Preload всего подряд просто переставляет очередь запросов. Реально помогли ровно два: шрифт и логотип в шапке.
- Уменьшение разрешения шейдера. Мы честно попробовали оставить живую WebGL-анимацию на мобильном, снизив рендер до 0,4× и 30 fps, потом до 0,25× и 20 fps. TBT упал с 594 до 273 мс и дальше не двигался: в Lighthouse мобильный рендер идёт без видеокарты, программно. Компромисс — на телефоне сначала показывается стоп-кадр этого же шейдера (17 КБ WebP), а анимация стартует после первого касания или скролла.
- Мелкие микрооптимизации. Снятие WooCommerce-ассетов, уборка лишнего CSS с главной, отложенный gtag — всё вместе дало примерно 2–3 балла. Значение они имеют только тогда, когда крупные проблемы уже закрыты.
Агентный веб-просмотр — новый блок, на который стоит смотреть
В мае 2026 Google перевёл категорию Agentic Browsing («Агентный веб-просмотр») из экспериментальной в основную конфигурацию Lighthouse, и вскоре она появилась в PageSpeed Insights. Оценка здесь не 0–100, а соотношение пройденных проверок: 3/3, 2/3 и так далее.
Вопрос, на который отвечает этот блок: может ли AI-агент прочитать страницу, понять её элементы управления и выполнить задачу, не угадывая? Вот как выглядят те три проверки в реальном отчёте:

- Дерево доступности. Есть ли у интерактивных элементов программные названия, корректные роли и правильная вложенность. То же, что нужно скринридеру — теперь нужно и агенту.
- Стабильность макета (CLS). Чтобы агент не кликнул туда, где кнопка была полсекунды назад.
- Файл
llms.txtв корне домена — машиночитаемое markdown-описание сайта: что это за проект, какие ключевые страницы, где документация.
Есть ещё четвёртая проверка — WebMCP, разметка форм и действий сайта так, чтобы агент мог ими пользоваться напрямую. Она появляется только у тех, кто этот экспериментальный протокол уже внедрил, поэтому у обычного сайта максимум — 3/3.
Наши 3/3 — не случайность: llms.txt мы выложили ещё весной, вместе со структурированными данными для страниц модулей, а доступность держим на 100 с первого дня. Что важно понимать: это не фактор ранжирования, и категория прямо помечена как развивающаяся. Но направление читается однозначно — качество сайта официально перестало быть вопросом только человеческого опыта. Половина ваших будущих «посетителей» — это ассистенты, которые приходят за ответом вместо пользователя.
Главный вывод
Наибольший прирост нам дали не мелкие оптимизации, а отказ от тяжёлых JS-анимаций в первом экране. Одна строка логотипов на requestAnimationFrame и одна библиотека на 1,3 МБ стоили больше баллов, чем все картинки, шрифты и сторонние скрипты вместе взятые.
Практический вывод для любого сайта: прежде чем сжимать картинки, откройте вкладку Performance и посмотрите, кто рисует каждый кадр. Дороже всего на странице — не то, что весит, а то, что работает непрерывно.
Что из этого забрать себе
- Не верьте одному прогону PageSpeed — берите медиану трёх, иначе спорите со случайным числом.
- Ищите
requestAnimationFrameи библиотеки, подключённые ради одной функции. - Всё, что ниже первого экрана, должно грузиться при приближении к нему, а не на старте.
- Шрифты и скрипты с чужих доменов — это рукопожатия, за которые платит первый показ текста.
- Проверьте свой
llms.txtи дерево доступности сейчас, пока блок «Агентный веб-просмотр» ещё никто не читает внимательно.
Если у вас на мобильном меньше 70 и непонятно, с чего начинать — напишите нам. Посмотрим ваш сайт и скажем, какие три вещи дадут наибольший прирост именно в вашем случае, без общих советов про «сожмите картинки».

