Блокнот · Заметка 001 · Скорость сайта
Где на статическом сайте прятались последние 18 баллов
Изображения сжаты. JavaScript занимает три файла. Фреймворков нет. Но Lighthouse всё равно показывал 82. Остальные баллы прятались в трёх местах, которые аудит назвал, но не объяснил.
Основатель BLN Global · · Читать 7 мин
Короткий ответ
Изображения и JavaScript уже были в порядке. Баллы терялись в трёх местах. Первая причина: проверка, которая должна была отсекать лишнюю работу, сама считывала разметку и заставляла браузер пересчитывать её. Вторая: файл стилей и внешние шрифты задерживали первую отрисовку на 600 ms. Мы встроили CSS в страницу, а шрифты перенесли на свой сервер. Третья: 6 из 24 предупреждений о контрасте оказались ложными. Полное сканирование затем нашло ещё 13 страниц, не попавших в выборку.
На сайте нет фреймворка. Python-скрипты генерируют обычный HTML, стили лежат в одном CSS-файле, JavaScript разбит на три небольших файла. Изображения хранятся в WebP и трёх размерах. Всё очевидное для быстрой загрузки мы уже сделали.
Мобильный балл застыл на 82.
Когда очевидные ошибки закончились, оставшиеся баллы пришлось искать в трёх разных местах. Чтобы найти их, мало было прочитать предупреждение аудита. Пришлось понять, что именно оно что ты хотел сказать нам пришлось разобраться.
Первое место: сама проверка, написанная, чтобы пропустить работу
Предупреждение Lighthouse «принудительная перекомпоновка» (forced reflow) означает, что JavaScript заставляет браузер раньше времени пересчитать макет страницы. Обычно браузер накапливает изменения стилей и применяет их разом. Если между ними код запрашивает размеры, браузеру приходится сразу выполнить весь накопившийся пересчёт.
Предупреждение показывало строку в исходнике, но в ней не было видимого замера. Нашли за три захода.
- В первом заходе
getComputedStyleвызовы убрали. Балл не изменился. - На втором круге показался настоящий виновник: код, написанный, чтобы пропускать лишнюю работу,
if (window.scrollY > 0)проверка. Она сама читала параметры макета. Строка, которая должна была пропустить работу, запускала именно её. - На третьем проходе мы удалили функцию целиком. Начальное состояние уже задавал CSS. JavaScript не требовалось собирать его заново.
Остался ещё один шаблон void element.offsetWidth было. Это известный приём, который намеренно запускает перерасчёт макета, чтобы перезапустить переход. Вместо него два последовательных requestAnimationFrame поставили. Работает так же и не ломает сетку.
function yenidenBaslat(fn) { /* Если вкладка открыта в фоне, rAF не запустится и демо останется пустым. */ if (document.hidden) { fn(); return; } requestAnimationFrame(function () { requestAnimationFrame(fn); }); }
Вывод такой: предупреждение о производительности чаще говорит не «ты делаешь слишком много», а «ты делаешь это не вовремя».
Второе место: 600 ms до первой отрисовки
Второе предупреждение касалось ресурсов, блокирующих первую отрисовку. При открытии страницы браузер сначала загружал и разбирал стили, затем шрифты. До этого экран оставался пустым. Задержка составила 600 миллисекунд.
Файл стилей встроили в страницу
Отдельный CSS-файл на втором визите приходит из кеша. Это плюс. Но на первом визите он добавляет лишний запрос туда-обратно, а первый визит как раз видит человек из поиска. При публикации мы минифицируем CSS и встраиваем в страницу. Проверили: после Brotli встроенный вариант оказался меньше, чем отдельный файл вместе со вторым запросом.
Шрифты перенесли на свой сервер
Установка из Google Fonts цеплялась к двум разным источникам: стиль шел с fonts.googleapis.com, для файлов fonts.gstatic.com. Для каждого нового источника браузеру нужны DNS-запрос, TLS-рукопожатие и новое соединение. Если убрать его, замеры покажут:
Шрифтовая загрузка турецкой страницы. Вместо 6 файлов с 2 внешних источников она получает 3 файла с одного.
unicode-range По полям unicode-range отделили подмножества, которые турецкая страница реально скачает (latin + latin-ext), и измерили каждый файл. У другой стороны в этом репозитории assets/fonts реальный вес файлов.Как уменьшались шрифты и где пришлось заплатить за десять языков, вынесли в отдельную заметку: десять языков, три файла шрифтов.
Третье место: где инструмент ошибся
Проверка доступности выдала 24 предупреждения о контрасте цветов. Если бы мы исправили их вслепую, половина дизайна сломалась бы. 6 предупреждений оказались ложными.
Причина content-visibility:auto Так и было. Эта настройка откладывает обработку блоков за пределами экрана и ускоряет первую отрисовку. То, что нужно. Но расчётный цвет отложенного блока не попадал в инструмент аудита, и тот сообщал: «белое на белом». На экране такой проблемы не было.
Реальные коэффициенты мы по одному измерили в браузере. 18 предупреждений подтвердились, и мы их исправили. В 6 случаях ошибся инструмент. content-visibility убрали только из двух проблемных разделов, в остальных оставили.
Предупреждение аудита ещё не доказывает, что перед тобой ошибка. Сначала проверь его, потом исправляй. На этом сайте мы ещё дважды столкнулись с такой ситуацией. Первый случай в заметке про проверку языка рассказываем.
И рана, которую мы сами открыли
Чтобы верстка не прыгала, всем картинкам width и height Добавили атрибут. Ход верный: браузер резервирует место под картинку ещё до загрузки. Но демо-блоки сломались: часть картинок нарисовалась шириной 58 пикселей и высотой 1500 пикселей.
Причина такая: в HTML width и height атрибуты дают подсказку, а в CSS height заполняют значение. В макете для этих блоков оставили только aspect-ratio задали, height пустовало. Атрибут лег туда и перебил пропорцию. Правка в одну строку:
img{max-width:100%;height:auto;display:block}
Где сканирование правда помогает
Главная набрала 100, и казалось, что все готово. Нет. Мы мерили выборку. У сайта все 150 страниц прогнали по одному через Lighthouse, и вышло вот что: 137 страниц получили 100 по всем четырем разделам, не 13 страниц. Доступность, лучшие практики и SEO на каждой странице получили 100. Просадка была только в производительности.
Одиннадцать из тринадцати страниц были на одном языке: арабском. Минимум: 83.
/ar/safecoast/ 83 /ar/laya/ 84 /ar/localmind/ 84 /ar/adforge/ 85 /ar/garanti/ 85 /ar/gtin/ 86 /ar/visionguard/ 86 /ar/ 89 /ar/growthos/ 97 /ar/dream/ 99 /ar/twin/ 99
Lighthouse сам назвал причину: Веб-шрифт загружен → ar.woff2. Арабский шрифт приходил поздно и заново собирал страницу. Метрики запасного арабского шрифта сильно отличались от Noto Sans Arabic, поэтому сдвиг получался большим. Исправили тем же решением, которое уже приняли для Martian Mono: обмен не так optional.
CLS на арабских страницах
Главный урок здесь не про скорость. Взять выборку и сказать «сайт 100» значит заявить о странице, которую ты не мерил. Поэтому пятнадцать страниц месяцами жили с 83 баллами. Мы туда просто не смотрели.
Предел результата
Оценка получена в лабораторных условиях с заданным устройством и сетью. Телефон и связь реального посетителя могут быть хуже. При повторных замерах одна и та же страница набирает от 98 до 100. Оценка 100 означает не «быстро для всех», а «измеримые помехи устранены». Когда накопятся данные посетителей, ориентируйся на полевые данные Chrome.
Общее у этих трёх правок одно: ни одна не требовала делать больше работы. Мы убрали работу, которая запускалась не вовремя или вообще была лишней.