Ми були впевнені, що сайт швидкий. Сервер віддає сторінку за 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 і незрозуміло, з чого починати — напишіть нам. Подивимось ваш сайт і скажемо, які три речі дадуть найбільший приріст саме у вашому випадку, без загальних порад про «стисніть картинки».


