vyvchy
    Теми розділу

    10 · Performance testing

    Фронтенд-продуктивність і Core Web Vitals

    Зміст

    Класична сцена: перф-звіт по бекенду зелений, p95 тримається в межах кількох сотень мілісекунд, а підтримка того ж дня приймає скарги «сайт гальмує». Обидві сторони кажуть правду — просто міряють різні речі. Серверні метрики закінчуються на першому байті відповіді; користувацький досвід із нього тільки починається. Далі браузер має скачати решту ресурсів, розібрати розмітку, виконати JavaScript, порахувати геометрію й намалювати кадр — і саме там живе більшість скарг на «повільно».

    Ця глава — єдиний повний виклад клієнтської продуктивності (client-side performance) на сайті: решта глав про метрики поверх браузера посилаються сюди. Механіка, поверх якої стоять ці метрики — життєвий цикл запиту, конвеєр рендерингу, HTTP, Waterfall як інструмент DevTools, — розібрана в розділі «Веб і мережі для AQA»; тут ідеться про метрики над нею.

    Швидкий бекенд ще не швидка сторінка

    Зона відповідальності бекенда закінчується на (time to first byte) — часі від старту запиту до першого байта відповіді. Це верхня межа впливу сервера й водночас нижня межа будь-якої клієнтської метрики: і містять у собі вивантаження попередньої сторінки, встановлення зʼєднання, редиректи й решту складників TTFB. Нульовий бекенд не дає нульового LCP. Дзеркально: TTFB і FCP — діагностичні метрики поганого LCP, перша вказує на повільну відповідь сервера, друга — на ресурси, що блокують рендеринг.

    Зона браузера

    Зона впливу бекенда

    Перехід на сторінку

    TTFB: перший байт

    FCP: перший вміст

    LCP: основний вміст

    INP: відповідь на взаємодію

    CLS: зсуви за весь життєвий цикл

    Зона браузера

    Зона впливу бекенда

    Перехід на сторінку

    TTFB: перший байт

    FCP: перший вміст

    LCP: основний вміст

    INP: відповідь на взаємодію

    CLS: зсуви за весь життєвий цикл

    Клієнтський набір покриває три боки досвіду — завантаження, інтерактивність і візуальну стабільність. Двох останніх бекенд не бачить узагалі, і залежать вони від того, чого немає в жодному серверному звіті: від пристрою, мережі, фонових процесів і поведінки користувача. Заразом варто відмовитися від звички міряти «час завантаження» подіями load і DOMContentLoaded — вони погані рівно тим, що не обовʼязково відповідають тому, що користувач бачить на екрані. Ціна повільності вимірна, але цифри цитують з атрибуцією, як чужі кейс-стаді: BBC втрачали ще 10 % користувачів на кожну додаткову секунду завантаження, покращення LCP на 31 % дало Vodafone +8 % продажів.

    Core Web Vitals: набір, пороги і 75-й перцентиль

    Web Vitals — ініціатива Google, сенс якої в тому, щоб власнику сайту не треба було бути експертом з продуктивності. — підмножина, що стосується всіх сторінок і виводиться в усіх інструментах Google; кожна метрика набору вимірна в полі й описує реальний результат користувача.

    МетрикаЩо міряє«good»«poor»
    LCPзавантаження основного вмісту≤ 2500 мспонад 4000 мс
    INPшвидкість відгуку на дії користувача≤ 200 мспонад 500 мс
    CLSвізуальна стабільність≤ 0,1понад 0,25

    Порогів у кожної метрики два, а не один: між ними лежить категорія «needs improvement». Ті самі три категорії експортує бібліотека web-vitals константами — LCPThresholds дорівнює [ 2500, 4000 ], INPThresholds[ 200, 500 ], CLSThresholds[ 0.1, 0.25 ].

    Окремо про межу включення, бо саме її читач копіює в поріг свого тесту. web.dev формулює пороги із включенням межі («200 мілісекунд або менше»), а Google Search Central у документі про ранжування переказує їх суворіше — «less than 200 milliseconds». Розбіжність не косметична: за методикою порогів рівно 200 мс — це ще «good», і цю ж редакцію підтверджують константи бібліотеки. Тому в поріг тесту беруть «або менше», а формулювання документа про ранжування читають як популярний переказ, а не як означення метрики.

    Друга половина правила — зріз розподілу. Ціль виконується не «в середньому», а на 75-му завантажень, окремо для мобільних і десктопних, і сторінка проходить лише коли на ньому виконано ціль по всіх трьох метриках одночасно. Вибір перцентиля — компроміс: на 100 візитах достатньо пʼятьох викидів, щоб зіпсувати 95-й перцентиль, і треба вже 25, щоб зрушити 75-й. Самі пороги виводять із досліджень сприйняття (де це діапазон, а не одне число) і одночасно з досяжності. Склад набору не вічний — 2024 року FID вивели, коли стабільною метрикою став .

    LCP: коли зʼявився основний вміст

    LCP (Largest Contentful Paint) звітує час рендерингу найбільшого зображення, текстового блоку або відео у видимій області — відносно моменту переходу на сторінку. Гіпотеза, на якій усе стоїть, названа в специфікації прямо: найбільше малювання під час завантаження ймовірно позначає значущу для користувача подію. Кандидати — обмежений перелік із пʼяти класів елементів, і рахується лише видима у viewport частина.

    Найважливіше для розуміння виміру — метрика змінюється в часі. Браузер віддає новий запис щоразу, коли найбільший елемент змінюється, тож в аналітику треба слати останній запис, а не перший. Видалення елемента з viewport чи навіть із DOM звання найбільшого з нього не знімає — знімає лише поява більшого. Правило зроблене заради каруселей, але воно ж ламає виміри на сайтах зі сплеш-екранами-плейсхолдерами.

    Далі — пастки, які бʼють саме по автотестах:

    • Перша ж взаємодія зупиняє вимір. Алгоритм зупиняється на скролі або вводі, бо саме вони найімовірніше додають новий вміст. Тест, який одразу після goto скролить сторінку, вимір руйнує; якщо ввід стався до появи основного вмісту, результату може не бути взагалі.
    • Фонова вкладка мовчить. У фоні записи не видаються до фокуса, тож для таких сторінок інструменти Google LCP не звітують.
    • SPA не скидає метрику. LCP привʼязаний до завантаження документа й не скидається ні при відновленні з bfcache, ні при навігації в межах того самого документа.
    • зображення без Timing-Allow-Origin не віддавали час рендерингу — звідси на вигляд неможлива картина «LCP раніший за FCP».

    Специфікація сама визнає, що API стоїть на евристиках і схильний до похибок. Звідси й розходження з лабораторією: елемент, який лабораторія визнала LCP-елементом, може бути не тим, що бачить користувач — через розмір екрана, персоналізацію, A/B-тест, набір шрифтів у системі. Якщо причина поганого LCP неясна, розкладає його на підчастини — TTFB зображення, до початку завантаження, тривалість завантаження й затримка рендерингу.

    INP: чи встигає сторінка відповісти

    INP (Interaction to Next Paint) оцінює чутливість сторінки на дії користувача: дивиться на затримку всіх взаємодій за візит і віддає одне число. Мотив арифметичний — за даними Chrome, 90 % часу на сторінці користувач проводить після її завантаження. Відповіддю вважається візуальний відгук у наступному намальованому кадрі, а не завершення всіх наслідків взаємодії: мережеві запити й асинхронні оновлення UI в INP не входять.

    Взаємодія — це група обробників одного логічного жесту (pointerdown, pointerup, click), яка збирається за interactionId. Затримка розкладається на три частини.

    input delay

    processing duration

    presentation delay

    Користувач натиснув

    Перший колбек

    Колбеки завершено

    Наступний намальований кадр

    input delay

    processing duration

    presentation delay

    Користувач натиснув

    Перший колбек

    Колбеки завершено

    Наступний намальований кадр

    Підсумкове число — найдовша взаємодія без урахування викидів: на сторінках із багатьма взаємодіями відкидається одна найгірша на кожні 50, а здебільшого взаємодій менше за 50, тож звітується просто найгірша. Рахується INP у момент виходу зі сторінки. Пороги: до 200 мс включно — добре, понад 200 і до 500 мс включно — потребує покращення, понад 500 мс — погано.

    Тепер найдорожче для AQA. INP спостерігає лише три типи взаємодій: клік мишею, тап і натискання клавіші; наведення, зум і скрол не враховуються, а неперервні події (mousemove, wheel, drag) виключено, бо API не має способу осмислено їх агрегувати. І головне обмеження ідеї «зміряти INP автотестом»: подія з isTrusted === false у вимір не потрапляє взагалі. Синтетична подія, породжена скриптом через dispatchEvent, метрики не дає — жодної. Тому сторінка може взагалі не мати значення INP, і серед названих причин прямо стоїть «сторінку відкрив бот або headless-браузер, якому не прописали взаємодій».

    Лабораторія тут особливо слабка: INP міряє чутливість саме в ті моменти, коли користувач сам вирішив взаємодіяти, і скриптована поведінка моменту не вгадує. Без мобільного viewport браузер додає 300 мс затримки після тапу — вона входить в INP, але не в . Історична довідка, яку досі питають: INP — наступник FID, що міряв затримку введення лише першої взаємодії.

    CLS: чому кнопка тікає з-під пальця

    (Cumulative Layout Shift) кількісно описує візуальну стабільність: як часто користувач бачить несподівані зсуви розкладки, які збивають читання й змушують натиснути не те посилання. Зсув фіксується, коли видимий елемент змінює початкову позицію між двома кадрами; поява нового елемента чи зміна розміру наявного зсувом не є — доти, доки це не посуває інші видимі елементи.

    Оцінка одного зсуву рахується за нормативною формулою специфікації: частка впливу × частка відстані (приклад із джерела: 0,75 × 0,25 = 0,1875). Перший множник — обʼєднання видимих площ нестабільних елементів у поточному й попередньому кадрі, поділене на площу viewport; другий — найбільше зміщення, поділене на більший вимір viewport.

    А от зведення окремих зсувів в одне число — місце, де половина статей в інтернеті застаріла. CLS — це найбільша серія оцінок зсуву за життєвий цикл сторінки, а не сума всіх зсувів. Серією (session window) вважають зсуви підряд із проміжком менше 1 секунди між ними й загальною тривалістю вікна не більше 5 секунд. Сумою CLS справді був раніше, і специфікація й досі описує його як суму — але сама позначає ці означення ненормативними, бо API таких величин не віддає: він віддає лише значення окремого зсуву на кадр, а зведення в метрику робить . Нормативний внесок специфікації — формула одного зсуву й вікно виключення за вводом: зсуви протягом 500 мс після вводу отримують hadRecentInput і можуть бути виключені, бо поганий не всякий зсув, а той, якого користувач не очікує. Прапорець ставиться лише для дискретних подій; скрол і зум «недавнім вводом» не вважаються.

    Тепер пастка, на якій ламаються баг-репорти. API віддає до пʼяти елементів, що найбільше долучилися до зсуву, — але sources показує зсунуті елементи, а не той, що їх зсунув. Якщо банер, який довантажився зверху, штовхнув увесь вміст під собою, у списку буде тільки штовхнуте: специфікація прямо каже, що визначити справжню першопричину браузер не може. Тож «винний елемент» зі звіту читають як «постраждалий» і шукають причину вище за потоком.

    Причини передбачувані — асинхронне довантаження, зображення без заданих розмірів, шрифт за , сторонні банери, — і систематично цього не видно в розробці: тестові зображення вже в кеші, локальні занадто швидкі. Перед автотестом варто памʼятати, що метрика міряється лише в Chromium і що на сторінці з iframe функція onCLS() формально міряє DCLS: API не бачать вмісту фреймів навіть свого .

    Допоміжні метрики: FCP, довгі задачі й TBT

    FCP (First Contentful Paint) позначає перший момент, коли користувач узагалі щось бачить: текст, зображення, <svg> або небіле <canvas>. Ціль — 1,8 секунди або менше, понад 3,0 секунди — «poor». У Core Web Vitals FCP не входить: це допоміжна метрика для діагностики поганого LCP, і вона навмисно грубіша за людське око — текст кольору фону користувач не бачить, а для FCP він рахується. FCP відповідає на питання «чи взагалі щось відбувається?», LCP — «чи готове те, заради чого я прийшов»: сплеш-екран FCP закриває, а користувачеві не дає нічого.

    (long task) — задача циклу подій тривалістю понад 50 мс. Поки вона виконується, головний потік заблокований, і взаємодія посеред неї просто чекає: браузер не може перервати вже розпочату задачу. Поріг виведено арифметично з моделі RAIL, яка вимагає відповіді на ввід менш ніж за 100 мс — 50 мс на добігання поточної задачі плюс 50 мс на саму реакцію. Наслідок, який найчастіше й описують у баг-репорті: сторінка виглядає повністю намальованою й не дає з собою взаємодіяти. У панелі Performance довгу задачу позначає червоний трикутник, і панель веде від події до її ініціатора аж до рядка коду, окремо попереджаючи про примусовий reflow.

    TBT (Total Blocking Time) зводить довгі задачі в одне число: сумарний час після FCP, коли головний потік був заблокований настільки, що сторінка не могла реагувати на ввід. Блокувальним вважається час задачі понад 50 мс, і TBT — сума таких надлишків; ціль — менше 200 мс на середньому мобільному залізі. Метрика лабораторна: у полі її міряти не радять через розкид. Вона краща за старий TTI, бо менш чутлива до викидів: три задачі по 51 мс і одна задача на 10 секунд відсувають TTI однаково, хоча TBT дає 3 мс проти 9950 мс.

    Головне обмеження: TBT — розумний лабораторний INP, але не його заміна. Він дає хибні тривоги (позначає проблему там, де користувач у цей момент не взаємодіє) і має пропуски (не ловить проблем від самих взаємодій). Саме тому в Lighthouse 10 TBT важить найбільше серед метрик — 30 % підсумкового балу: сам INP інструмент виміряти не може.

    Лабораторія і поле: два різні виміри

    Лабораторні інструменти збирають дані в керованому середовищі із заздалегідь заданими пристроєм і мережею — таке середовище ще називають синтетичним. Польові збирають дані від реальних відвідувачів; й RUM (Real User Monitoring) — взаємозамінні назви.

    Мета лабораторного тесту — прибрати якнайбільше змінних, щоб результат був відтворюваним: це найкращий спосіб перевірити фічу до релізу й зловити заздалегідь. Але кероване середовище навмисно не відтворює реальний розкид: один пристрій, одна мережа, одна географія, майже завжди холодний кеш і жодного bfcache.

    Найголовніше про польові дані: це розподіл, а не число. Якщо інструмент показує одне число — це точка розподілу, і для Core Web Vitals це 75-й перцентиль; тому лабораторні 3,0 с проти польових 1,8 с — не помилка вимірювання. Звідси правило пріоритету: коли є обидва набори, пріоритети ставлять за польовими даними, і лабораторний вимір польового не заміняє. Зворотний бік теж корисний: поле бачить лише тих, кому сайт узагалі завантажився, і саме лабораторія допомагає дотягтися до повільних мереж та слабких пристроїв.

    Якщо піднімати власний RUM, три правила економлять місяці:

    • Не рахуйте середнє — воно не описує жодного реального сеансу, а невелика група на дуже повільних мережах може взагалі його не зрушити. Звіт має показувати кожну метрику на 75-му перцентилі, а поруч — 90-й і 95-й.
    • Стежте за моментом відправки: CLS остаточний лише коли сторінка починає вивантажуватися, а beforeunload і unload ненадійні й можуть вибити сторінку з bfcache. Правильний момент — visibilitychange при переході в hidden.
    • Не діліть метрики «до і після дати деплою» — кешування на рівні HTTP, service worker чи CDN ламає цю межу; кожному потрібна унікальна версія.

    Готове поле «безкоштовно» дає CrUX (Chrome User Experience Report) — датасет про те, як реальні користувачі Chrome переживають популярні сторінки вебу. Він показує розподіл за 28-денне вікно й віддає метрики лише як статистичні агрегації; сирих значень окремих сеансів API не дає взагалі, а p75 читається буквально: щонайменше 75 % завантажень мали значення не більше за наведене. Головне обмеження: у датасеті є не всі сторінки — сторінка має бути публічно знаходною і мати достатньо відвідувачів для статистичної значущості. Для нового або малотрафікового сайту польових даних там просто не буде, і це не діагноз «сайт швидкий».

    Бал продуктивності Lighthouse: що це число означає

    Підсумковий бал — зважене середнє балів окремих метрик, і самих балів метрик у звіті не показують. У Lighthouse 10 ваги такі: FCP — 10 %, Speed Index — 10 %, LCP — 25 %, TBT — 30 %, CLS — 25 %; у Lighthouse 8 набір був інший. Практичний наслідок жорсткий: бали різних версій інструмента незіставні, і «було 72, стало 68» після оновлення нічого не означає.

    Найважливіше про природу числа: сире значення метрики перетворюється на бал 0–100 за положенням у логнормальному розподілі, побудованому на даних HTTP Archive. Тобто бал Lighthouse — це позиція відносно решти вебу, а не абсолютна оцінка: 25-й перцентиль HTTP Archive дає бал 50, 8-й перцентиль — бал 90, а приблизно з 0,96 починається зона спадної віддачі, де підняти бал із 99 до 100 приблизно так само дорого, як із 90 до 94.

    І окремо про стрибучість, бо на неї спирається головне процесне правило. Коливання балу здебільшого спричинені не інструментом, а зміною умов вимірювання: A/B-тести й різні рекламні креативи, маршрутизація трафіку, різні пристрої, розширення браузера, антивірус. Сам інструмент радить думати про продуктивність як про розподіл балів, а не про одне число — це і є пряма підстава правила «один прогін Lighthouse не є гейтом». Як зробити з цього робочий гейт у — тема глави «Performance-тести в CI/CD».

    Waterfall у машиночитному вигляді: Resource Timing і Navigation Timing

    Колонка Waterfall у вкладці Network — інструмент розділу «Веб і мережі для AQA»; тут важливо інше: той самий таймлайн доступний у коді. Специфікація Resource Timing означує PerformanceResourceTiming — повні мітки часу кожного ресурсу, і поля цього інтерфейсу утворюють таймлайн завантаження: редиректи, fetchStart, DNS, зʼєднання, requestStart, responseStart, responseEnd. Саморобний замір через onload так не вміє — він не розкладає час на фази й не бачить ресурсів, оголошених у розмітці.

    Запис несе не тільки час: розміри тіла, код відповіді, протокол — і машинну позначку blocking або non-blocking, тобто «що блокує рендеринг» видно прямо з таймлайна. Звідси готові перевірки: стиснення працює, якщо decodedBodySize не дорівнює encodedBodySize, а transferSize рівний нулю означає влучання в кеш.

    Три пастки роблять цей таймлайн брехливим:

    • Ресурс може не потрапити в таймлайн узагалі. Ресурс, зарубаний отримання (mixed content, CORS, CSP), туди не додається — його там просто немає, а не «є з нульовим часом».
    • Крос-оріджин ресурс дає непрозорий запис. Без дозволу таймінгу занулено редиректи, DNS, зʼєднання, TLS, запит і початок відповіді, а для крос-оріджин відповіді з CORS — ще й розміри тіла та тип вмісту; лишається сама тривалість — саме тому сторонні скрипти виглядають «одним суцільним блоком». Маскування знімає заголовок Timing-Allow-Origin, але навіть із дозволом браузер має право лишити розміри нульовими.
    • Буфер записів скінченний. Типово буферизується лише 250 записів ресурсів, тож довгий сценарій із сотнями запитів мовчки втрачає хвіст.

    Ще дрібниця з великими наслідками: responseStart може бути проміжною відповіддю (103 Early Hints), тож наївне «TTFB — це responseStart - requestStart» на такій сторінці міряє не те.

    Життєвий цикл самого документа описує сусідня специфікація — Navigation Timing. Її запис у таймлайні один, дістається як перший елемент performance.getEntriesByType("navigation") і успадковує все від запису ресурсу. Крім звичних міток (domInteractive, domContentLoadedEventStart, domComplete, loadEventEnd) у ньому є три поля, що прямо стосуються достовірності виміру: тип навігації з рівно трьома значеннями — navigate, reload, back_forward, за яким відсіюють прогони «назад/вперед» від холодного завантаження; confidence — обʼєкт, чиє поле value дорівнює high або low, де low означає, що метрики можуть не описувати реальний досвід на цьому пристрої (канонічна підстава тези «одне вимірювання не є оцінкою»); і redirectCount. Окремої мітки «взяли з кешу» тут немає: робота з HTTP-кешем уже врахована в requestStart, responseStart, responseEnd.

    Про правила кешування — глава «Кешування», про фази запиту як явище — «Життєвий цикл запиту», про читання Waterfall руками — «DevTools: вкладка Network і дебаг».

    Виміри в автотестах: Performance API

    — не одна специфікація, а група стандартів. Одиниця виміру в ній одна: PerformanceEntry з полями name, duration, startTime і типом. Родина широка — від LargestContentfulPaint і LayoutShift до PerformanceServerTiming, який витягує серверні метрики із заголовка Server-Timing, тобто робить місток між фронтендною метрикою й серверною стороною.

    Перше, що треба зрозуміти, — чому тут не годиться Date.now(). Date рахує час за системним годинником, а той зсувається й підкручується: замір «до і після» може дати додатне число, відʼємне або нуль, і це сказано в специфікації прямо. Звідси правило поділу: монотонний годинник — для вимірів, настінний — для показу користувачеві. performance.now() повертає тривалість від початку відліку, і різниця двох послідовних викликів відʼємною бути не може. Записи читають через PerformanceObserver — він повідомляє про нові в момент появи, а підтримку типу перевіряють через supportedEntryTypes, а не try/catch.

    І тепер найдорожче застереження всієї теми: зняти запис PerformanceObserver ще не означає виміряти метрику. Джерело перелічує чотири розбіжності поіменно на прикладі FCP: API віддає запис і для сторінки у фоновій вкладці, хоча метрика такі сторінки має ігнорувати; при відновленні з bfcache API запису не дає, а метрику міряти треба; крос-оріджин iframe можуть не потрапити у виміри; API рахує від початку навігації, а для попередньо відрендерених сторінок відлік має йти від activationStart. Бібліотека web-vitals бере ці розбіжності на себе — крім випадку з iframe. І є випадки, коли зміряти метрику в JavaScript неможливо взагалі.

    Наша практика (не канон). Далі — те, як це роблять команди, з якими ми працювали; окремого стандарту чи специфікації під це немає. Спокуса «додати перф-перевірку в e2e» велика, але корисним виявляється вузький зріз: детерміновані факти з таймлайна, а не самі Core Web Vitals. Причини названі вище: INP у сценарії вимірний лише настільки, наскільки вдається зімітувати реалістичні взаємодії, LCP руйнується першим же скролом сценарію, CLS у сценарії неповний. Натомість добре працюють перевірки, що проходять або ні незалежно від заліза : кількість редиректів, вага ресурсів, блокування рендерингу, влучання в кеш.

    import { test, expect } from '@playwright/test';
    
    test('головна віддається без редиректів і без зайвої ваги', async ({ page }) => {
      await page.goto('/', { waitUntil: 'load' });
    
      const nav = await page.evaluate(() => {
        const [entry] = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
        return { type: entry.type, redirectCount: entry.redirectCount };
      });
    
      expect(nav.type).toBe('navigate');
      expect(nav.redirectCount).toBe(0);
    
      const heavyScripts = await page.evaluate(() =>
        (performance.getEntriesByType('resource') as PerformanceResourceTiming[])
          .filter((r) => r.initiatorType === 'script' && r.transferSize > 300_000)
          .map((r) => r.name),
      );
    
      expect(heavyScripts, `завеликі скрипти: ${heavyScripts.join(', ')}`).toHaveLength(0);
    });

    Три застереження до цього коду, усі з попередніх підрозділів. transferSize рівний нулю означає влучання в кеш, тож на прогрітому контексті перевірка мовчатиме — контекст має бути свіжим. Нульові розміри віддає й крос-оріджин ресурс без Timing-Allow-Origin, тож саме сторонні скрипти такий фільтр пропускає. І буфер ресурсів типово тримає 250 записів, тож у довгому сценарії читати його треба одразу після завантаження, а не наприкінці тесту.

    Вплив на SEO: досвід сторінки в пошуку

    Основні системи ранжування Google заохочують вміст, що дає добрий (page experience), і концентруватися на одному-двох аспектах Google прямо не радить. Самоперевірка, яку він пропонує, — шість питань: чи добрі Core Web Vitals; чи віддаються сторінки безпечно; чи коректно вміст показується на мобільних; чи немає надміру реклами; чи немає навʼязливих перекривних вікон; чи легко відрізнити основний вміст від решти. Перелік прямо названо не вичерпним.

    Що з цього справді впливає на позиції: Core Web Vitals системи ранжування використовують — це підтверджено прямо, а дані для чинника беруться з CrUX. Формулювання рекомендації варто цитувати акуратно: Google «радить» мати добрі Core Web Vitals і каже, що вони «узгоджені» з тим, що заохочують системи ранжування, — а не «гарантують вищу позицію». Єдиного «сигналу досвіду сторінки» не існує, а оцінка переважно посторінкова.

    І межа, яку варто памʼятати дослівно, бо саме на ній будуються найгучніші обіцянки в тендерах: зелений звіт не гарантує високих позицій. Гнатися за ідеальним балом заради SEO Google називає сумнівним витрачанням часу. Релевантність переважає: погана продуктивність не викине релевантну сторінку з видачі.

    Типові помилки

    • «Бекенд тримає p95 200 мс — зі швидкістю все добре» → виглядає як закритий пункт, а насправді TTFB лише нижня межа клієнтських метрик: інтерактивності й візуальної стабільності бекенд не бачить.
    • «CLS — це сума всіх зсувів за сесію» → виглядає як означення зі специфікації, а насправді сумою метрика була раніше, і саме означення сумою специфікація позначає ненормативним.
    • «У sources записаний елемент — ось хто зсунув розкладку» → виглядає як готовий баг-репорт, а насправді там зсунуті елементи; причину браузер визначити не може й не намагається.
    • «Ми міряємо INP автотестом» → виглядає як метрики, а насправді в лабораторії INP вимірний лише настільки, наскільки вдається зімітувати реалістичні взаємодії, а подія, породжена скриптом через dispatchEvent, у вимір не потрапляє взагалі.
    • «Lighthouse 94 — Core Web Vitals зелені» → виглядає як еквівалент, а насправді бал це позиція відносно розподілу HTTP Archive у лабораторії, а набір польовий і рахується на 75-му перцентилі реальних користувачів.
    • «У CrUX по нашому сайту порожньо — значить, усе гаразд» → виглядає як зелений результат, а насправді сторінка просто не набрала трафіку для статистичної значущості.
    • «Порахували всі запити через таймлайн ресурсів» → виглядає як повний таймлайн, а насправді буфер типово тримає 250 записів, а крос-оріджин ресурси без Timing-Allow-Origin віддають лише загальну тривалість.

    Підсумок

    • починається там, де закінчується серверна. TTFB — верхня межа впливу бекенда й нижня межа будь-якої клієнтської метрики.
    • Core Web Vitals — три метрики, дві межі в кожної і один зріз розподілу. Оцінка йде на 75-му перцентилі, окремо для мобільних і десктопних, і сторінка проходить лише коли всі три виконано одночасно.
    • Набір польовий за задумом. Лабораторія ловить регресію до релізу, але пріоритети ставлять за польовими даними, а поле — це розподіл, а не число.
    • Автотестом міряються не метрики, а факти. INP вимірний лише настільки, наскільки вдається зімітувати реалістичні взаємодії, LCP руйнується взаємодією — зате редиректи, вага ресурсів і влучання в кеш читаються з таймлайна детерміновано.
    • Кожен API має названу межу. Запис ще не метрика, sources показує постраждалих, а не винних, буфер ресурсів скінченний, а зелений звіт не гарантує ані швидкості для користувача, ані місця у видачі.

    Можливі питання

    • «Що таке Core Web Vitals і які в них пороги?» — питання на впізнавання. Сильна відповідь називає три метрики з тим, що кожна міряє, дає обидві межі (не лише «good») і одразу додає правило 75-го перцентиля — без нього пороги не мають сенсу.
    • «Бекенд швидкий, а користувачі скаржаться на гальма. Де шукати?» — перевіряють, чи розводите ви серверну й клієнтську частини. Очікують згадку, що TTFB лише початок, і перехід до FCP/LCP як діагностики завантаження та довгих задач як причини «намальовано, але не реагує».
    • «Як ви поміряєте Core Web Vitals у Playwright?» — пастка на межі інструмента. Слабка відповідь одразу пише код; сильна називає обмеження — INP вимірний лише настільки, наскільки вдається зімітувати реалістичні взаємодії (а подія з dispatchEvent не рахується взагалі), LCP зупиняється на першій взаємодії — і пропонує детерміновані перевірки з таймлайна плюс окремий лабораторний прогін.
    • «У нас Lighthouse 95, а Search Console показує "needs improvement". Хто бреше?» — дивляться, чи розумієте ви різницю лабораторії й поля. Ніхто не бреше: одне число з керованого середовища, друге — 75-й перцентиль реальних користувачів за 28-денне вікно.
    • «CLS у звіті 0,3, у списку елементів — блок із текстом. Це він винен?» — питання рівно про атрибуцію. Правильна відповідь: у списку зсунуті елементи, а не винуватець; шукати треба те, що зʼявилося або змінило розмір вище за потоком.

    Джерела

    Швидкий бекенд ще не швидка сторінка

    • web.dev — Why does speed matter? — утримання й доведення дії до кінця, чужі кейс-стаді BBC/Vodafone/redBus з атрибуцією, мегабайти коду та обмеження мобільних пристроїв.
    • web.dev — Time to First Byte (TTFB) — TTFB як час від старту запиту до першого байта відповіді.
    • MDN Glossary — Time to first byte (TTFB) — те саме означення в словнику MDN.
    • web.dev — Largest Contentful Paint (LCP) — LCP містить у собі TTFB і його складники; чому load і DOMContentLoaded не відповідають побаченому.
    • web.dev — First Contentful Paint (FCP) — FCP так само містить вивантаження попередньої сторінки, зʼєднання, редиректи й TTFB.
    • web.dev — Web Vitals — три боки досвіду; TTFB і FCP як діагностичні метрики поганого LCP; залежність від пристрою, мережі й поведінки користувача.

    Core Web Vitals: набір, пороги і 75-й перцентиль

    LCP: коли зʼявився основний вміст

    INP: чи встигає сторінка відповісти

    • web.dev — Interaction to Next Paint (INP) — означення й мотив «90 % часу після завантаження», три частини затримки, правило відкидання одна на 50 і момент підрахунку, пороги, три типи врахованих взаємодій, причини відсутності значення, iframe і розходження з CrUX, спадкоємність FID.
    • W3C — Event Timing API — групування подій через interactionId, закритий перелік типів і виключення неперервних подій, відкидання подій з isTrusted === false, округлення duration, записи без слухачів, означення першого вводу.
    • web.dev — The research and methodology behind Core Web Vitals thresholds — обидві межі INP на 75-му перцентилі.
    • web.dev — Why lab and field data can be different (and what to do about it) — чому лабораторія не вгадує момент взаємодії й чому 300 мс після тапу входять в INP, але не в TBT.
    • GoogleChrome/web-vitals — README — INP не звітується, якщо взаємодії не було.

    CLS: чому кнопка тікає з-під пальця

    • web.dev — Cumulative Layout Shift (CLS) — означення й ціль, що вважається зсувом, формула та приклад, «найбільша серія» і параметри вікна, попереднє означення сумою, вікно hadRecentInput і перелік дискретних подій, типові причини й чому дефекту не видно в розробці.
    • WICG — Layout Instability API — нормативна формула одного зсуву й нормативне вікно 500 мс, ненормативність означення сумою, до пʼяти елементів у sources і пряме застереження, що першопричини браузер визначити не може, ліниві й takeRecords().
    • web.dev — The research and methodology behind Core Web Vitals thresholds — друга межа CLS: понад 0,25 — «poor».
    • web.dev — Why lab and field data can be different (and what to do about it) — лабораторний CLS як підмножина польового і вплив персоналізації.
    • GoogleChrome/web-vitals — README — CLS лише в Chromium, DCLS замість CLS на сторінці з iframe, відсутність звіту для сторінки у фоні.

    Допоміжні метрики: FCP, довгі задачі й TBT

    • web.dev — First Contentful Paint (FCP) — означення, ціль і межа «poor», що вважається вмістом, різниця з LCP і випадок сплеш-екрана.
    • W3C — Paint Timing — FP і FCP як метрики поверх малювання, питання «чи взагалі щось відбувається?», грубість метрики щодо тексту кольору фону.
    • web.dev — Web Vitals — FCP і TBT як допоміжні метрики поза набором і роль FCP у діагностиці поганого LCP.
    • W3C — Long Tasks API — поріг 50 мс і його виведення з моделі RAIL, три випадки, які рахуються довгою задачею, наслідки для вводу, анімацій і скролу.
    • web.dev — Total Blocking Time (TBT) — означення й ціль, блокувальний час понад 50 мс, порівняння з TTI на числах, хибні тривоги й пропуски, «проксі, але не заміна», неперервність задачі для браузера.
    • Chrome DevTools — Analyze runtime performance — червоний трикутник довгої задачі, шлях від події до рядка коду, попередження про примусовий reflow.
    • MDN — Performance API — місце довгих задач і довгих кадрів анімації в родині інтерфейсів.
    • web.dev — Why lab and field data can be different (and what to do about it) — чому TBT не міряє «сторінка виглядає готовою».
    • Chrome for Developers — Lighthouse Performance scoring — вага TBT 30 % у Lighthouse 10.
    • GoogleChrome/web-vitals — README — FCP не звітується для сторінки, завантаженої у фоні.

    Лабораторія і поле: два різні виміри

    Бал продуктивності Lighthouse: що це число означає

    Waterfall у машиночитному вигляді: Resource Timing і Navigation Timing

    • W3C — Resource Timing — повні мітки часу ресурсу й порядок фаз, обмеженість саморобного заміру через onload, розміри тіла й позначка блокування рендерингу, три пастки: відсутність зарубаного ресурсу, маскування крос-оріджин запису і Timing-Allow-Origin, скінченний буфер.
    • MDN — PerformanceResourceTiming — готові формули фаз, машинні перевірки стиснення, кешу й протоколу, типовий розмір буфера 250 записів, responseStart як можлива проміжна відповідь.
    • Chrome DevTools — Network features reference — колонка Waterfall у вкладці Network як інструментальний бік того самого таймлайна.
    • W3C — Navigation Timing Level 2 — один запис навігації і спосіб його дістати, мітки життєвого циклу документа, три типи навігації, поле confidence, відсутність окремої мітки кешу.
    • MDN — PerformanceNavigationTiming — успадкування від запису ресурсу, redirectCount, activationStart і notRestoredReasons.
    • MDN — Window: load event — подія, якій Navigation Timing проставляє мітки loadEventStart і loadEventEnd.
    • MDN — Document: DOMContentLoaded event — подія, вартість обробників якої видно як різниця двох міток.

    Виміри в автотестах: Performance API

    • MDN — Performance API — група стандартів і таймлайн, PerformanceEntry як одиниця виміру, склад родини інтерфейсів і PerformanceServerTiming як місток до серверних метрик.
    • MDN — PerformanceObserver — підписка на нові записи, observe(), список у колбеку, takeRecords() і supportedEntryTypes.
    • W3C — High Resolution Time (Level 3) — чому Date.now() ненадійний, монотонний і настінний годинники, performance.now() як тривалість, необовʼязковість субмілісекундної роздільності.
    • web.dev — First Contentful Paint (FCP) — чотири розбіжності «запис API ≠ метрика» і випадки, коли зміряти метрику в JavaScript неможливо.
    • GoogleChrome/web-vitals — README — бібліотека бере розбіжності на себе, крім iframe; ціна зайвих викликів і роль прапорця buffered.
    • WICG — Layout Instability API — примусове забирання відкладених записів перед відправкою підсумку.

    Вплив на SEO: досвід сторінки в пошуку

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.