Категории
65 → 95 в PageSpeed: разбор ускорения сайта на Битриксе
23.10.2026 | из категории: Web-разработка
Главная страница сети логопедических центров весила 895 КБ, а самая крупная картинка появлялась только на пятой секунде. Ниже — что именно мы делали, в каком порядке, что это дало в цифрах и что не получилось. Всё проверялось замерами, а не на глаз.
01Задача и как мы мерили
На телефоне PageSpeed показывал 65, на компьютере — 98. То есть сайт «нормальный», но на телефоне посетитель ждёт картинку пять секунд. Отчёт называл три виновника: вес, блокирующие отрисовку файлы и чужие скрипты.
| Показатель | До | После |
|---|---|---|
| Балл, телефон | 65 | 95 |
| Балл, компьютер | 98 | 100 |
| Вес страницы | 895 КБ | 504 КБ |
| Первая отрисовка (FCP) | 2,0 с | 1,3 с |
| Главная картинка (LCP) | 5,8 с | 3,3 с |
| Задержка отклика (TBT) | 470 мс | 120 мс |
| Доступность | 84 | 96 |
Почему одного прогона мало
Пять прогонов подряд на одном и том же состоянии дали 60, 82, 87, 88, 88. Один прогон, где ядро сайта случайно успело выполниться уже после первой отрисовки, роняет TBT с 90 до 950 мс — и балл с 88 до 60. Поэтому сравнивать надо медиану трёх-пяти замеров, иначе будешь гоняться за случайностью.
Мой локальный Lighthouse показывал 65 там, где встроенный отчёт Google у владельца показывал те же 65, а после правок — 88 против 95 в отчёте Google. Условия симуляции у инструментов разные. Ориентироваться надо на список пунктов в отчёте, а спорить об абсолютном балле бессмысленно.
02Кэш, которого не было
Самая дорогая находка оказалась скучной: сервер не говорил браузеру, сколько можно хранить картинки формата webp и шрифты. Каждый заход — заново.
В отчёте это выглядело как 384 КБ «на выброс» из 895. Правило в .htaccess
закрывало jpeg, png, css и js, а webp, woff и woff2
в нём просто не были перечислены.
# было: три дня и только для части типов
ExpiresByType image/jpeg "access plus 3 day"
ExpiresByType text/css "access plus 3 day"
# стало: год и для того, что реально отдаётся
ExpiresByType image/webp "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
Проверять надо каждый файл из отчёта отдельным запросом. У этого сайта css, js и svg
отдаёт не Apache, а nginx, и правила из .htaccess к ним не применяются вообще:
они уже шли с трёхдневным сроком, прописанным в конфиге хостинга. То есть половина пункта
«кэш» из файла на диске не лечится — нужна панель хостинга или письмо в поддержку.
03Логотип на 349 КБ, который показывается тридцатью пикселями
Вес страницы складывается не только из фотографий. Здесь самый тяжёлый файл на главной — значок мессенджера.
max-logo.svg весил 349 КБ. Внутри SVG лежала зашитая растровая картинка
1000×1000 (253 КБ в base64), а показывался этот значок в шапке, подвале и блоке контактов
размером около тридцати пикселей.
Картинку внутри ужали до 128×128 и вложили обратно в тот же файл. Затем отрисовали старую и новую версию в браузере и сравнили попиксельно: средняя разница 0,1 из 255, максимальная — 2. Это уровень «не видно даже при увеличении».
Расширение файла ничего не говорит о его весе. Смотреть надо внутрь: SVG с вложенным растром весит как фотография, а рисуется как иконка.
04Bootstrap: 195 КБ → 76 КБ, и как я на этом сломал меню
Треть отчёта — «неиспользуемый CSS». Из библиотеки Bootstrap на сайте работала примерно десятая часть правил.
Порядок такой: обойти сайт по ссылкам (не только по карте сайта — карта отдавала 119 адресов,
обход нашёл 128) и собрать все классы, которые реально встречаются в разметке. Вышло 452 класса
из почти двух тысяч. Затем вырезать правила, где нет ни одного живого класса — но чужие правила
с элементом вместо класса (html, h1, *) оставлять всегда.
Первая сборка получилась 38 КБ вместо 195 — и меню на телефоне перестало открываться.
Вырезая «неиспользуемые» классы, я вырезал правила компонентов, которыми управляет скрипт:
у панели меню классы offcanvas и offcanvas-end в разметке есть,
а show и showing дописывает библиотека на лету — их в разметке нет,
и они попали под нож. Внешне это выглядит так: скрипт класс добавляет, а панель остаётся
за правым краем экрана со сдвигом 400 px. Ошибок в консоли при этом ноль.
Починка — не косметическая, а по смыслу: правила всех компонентов, которыми управляет скрипт (меню, модальные окна, выпадающие списки, аккордеоны, слайдеры, всплывающие подсказки), больше не вырезаются вообще. Файл вырос с 38 до 76 КБ — это 12,3 КБ в сжатом виде против 27,2 КБ исходных.
| Что проверяли кликом | Результат |
|---|---|
| Меню на телефоне, 390 px | открывается, 20 ссылок |
| Вложенный пункт внутри меню | раскрывается |
| Выпадающее меню «Наши центры» | открывается |
| Аккордеон на странице цен | раскрывается |
| Геометрия страницы против исходной | 799 элементов, 0 расхождений |
Урезанный набор стилей становится хрупким: если завтра понадобится новый класс Bootstrap, его придётся дописывать вручную. Оригинальный файл лежит рядом, откат — одна строка в шаблоне.
05Шрифты: 94 КБ → 57 КБ без потери вида
Два фирменных шрифта весили почти сто килобайт и грузились первыми, потому что ими набраны заголовки первого экрана.
В них были все символы, которые поддерживает шрифт, — включая те, которых на сайте нет никогда. Мы собрали все 180 разных символов, встречающихся в текстах всех страниц, добавили страховку (латиница целиком, вся основная кириллица, типографика) и вырезали остальное.
Проверка двойная: во-первых, сверили таблицу символов — ни один символ, который был в исходном шрифте, не пропал. Во-вторых, сняли страницу до и после: начертание то же, потому что контуры букв не менялись, вырезаны только лишние знаки.
Шрифт — это то, что видно всем посетителям. Менять его на системный ради балла нельзя: на этом сайте такая замена была бы видна сразу. Урезание по символам — другое дело, вид не меняется вовсе.
06Ядро Битрикса и мелочи, которые дают секунды
Дальше — самое неочевидное. Половину выигрыша дали не картинки, а то, что грузилось «по привычке».
Ядро отдавалось неминифицированным
Главный скрипт движка весил 543 КБ, и в отчёте висело «уменьшите размер кода JavaScript».
Рядом на сервере лежала готовая минифицированная версия того же файла — 245 КБ. Залили её на
место обычной. Проверка: объект BX создаётся, jQuery и Bootstrap на месте,
слайдер и аккордеон работают, ошибок в консоли нет.
Слайдер грузился на всех страницах
Библиотека слайдеров — 40 КБ — подключалась везде, хотя слайдер есть на шести страницах из ста тридцати. Добавили условие: грузить, только если на странице есть элемент слайдера. На главной её больше нет.
Предзагрузка того, что нужно сразу
Два шрифта и главная картинка теперь объявлены в шапке как preload. Первая
отрисовка: 1,9 → 1,3 секунды. Картинку предзагружаем только на тех страницах, где она есть, —
иначе остальные страницы качали бы файл, которого у них нет.
Стили, вшитые в шапку
Тридцать килобайт CSS лежали прямо в разметке страницы, пятью блоками. Их сжали до шестнадцати (убрали комментарии и лишние пробелы), число правил не изменилось — 301 до и после.
Заглушка для ленивых картинок
Пока настоящие фото не догрузились, вместо них показывается картинка-заглушка. Она весила 10 КБ (600×400) и при этом была больше контейнера. Ужали до 3,7 КБ ровно в той же пропорции: первая попытка с другой пропорцией сдвинула один блок на 1–2 пикселя, и это поймал замер геометрии, а не глаза.
07Агентный просмотр и доступность
В отчёте Google появились две новые категории, и обе были в плохом состоянии: агентный просмотр — 1 из 3, доступность — 84.
Агентный просмотр: 1/3 → 3/3
Это проверка того, насколько сайт понятен ИИ-агентам и AI-поисковикам. Три условия: дерево
доступности в порядке, вёрстка не прыгает и есть файл llms.txt со ссылками.
Файл llms.txt на сайте был, но проверка всё равно падала с формулировкой
«файл не содержит ссылок»: адреса в нём были записаны обычным текстом, а нужны ссылки
в разметке Markdown. Заодно не существовало файла agent.md, на который
llms.txt ссылался.
| Что было | Стало |
|---|---|
llms.txt: 0 ссылок в разметке | 31 ссылка |
llms-full.txt: 0 ссылок | 45 ссылок |
agent.md: нет файла (404) | есть, отдаётся как text/markdown |
Доступность: 84 → 96
- Четыре ссылки на соцсети были картинками без текста — добавили подписи для озвучки.
- Кнопка мобильного меню — тоже без имени, теперь «Открыть меню».
- Поля формы заявки шли без подписей вовсе: имя, телефон, почта, сообщение — подписали.
- Серый текст
#757575давал контраст 4,41 при норме 4,5 — затемнили до#707070, стало 4,75.
Пункт «размер цели» так и остался незакрытым: телефонная ссылка в шапке высотой 19 px при норме 24. Я примерил увеличение области нажатия — шапка сдвигалась на 1–2 пикселя, и я откатил. Из-за этого доступность 96, а не 100: за балл не стал двигать вёрстку.
08Страница с шестью плеерами: 32 → 89
Когда главная уже показывала 95, одна внутренняя страница упорно держалась на 47. Причём дело было не в шаблоне: остальные страницы того же типа давали 83–93.
Страница «Занятия с нейролингвистом» — обычный текст, одна картинка на 25 КБ. Всё остальное приезжало из вставок в её содержимом:
- шесть плееров Rutube — три ролика, каждый вставлен дважды: вариант для компьютера высотой 170 px и для телефона высотой 200 px;
- чат-виджет — четыре своих файла и своя копия jQuery 3.2.1 с Google CDN, хотя jQuery у сайта уже была;
- Google Tag Manager и рекламный бандл Яндекса с рекламного CDN.
Замер до правок: вес 1,4 МБ, 114 запросов, TBT 3,1–3,9 секунды, главная нить занята 7,4–8,7 секунды. LCP 8,9 секунды при картинке в 25 КБ — её просто не успевали показать.
Почему loading="lazy" не помог
Первым шагом я добавил отложенную загрузку всем шести плеерам
(loading="lazy"). TBT упал с 3,1 с до 140 мс, но в отчёте всё равно остались
три копии плеера по 679 КБ. Причина: на медленном соединении браузер
подтягивает ленивые iframe заранее, за 3000–4000 пикселя до экрана, а видео стоят не так
далеко. Отложенная загрузка спасает от мгновенной загрузки, но не от ранней.
Обложка вместо плеера
Надёжно режется только так: вместо живого плеера — картинка-кадр и кнопка, а настоящий
плеер подставляется по нажатию. Кадры взяты из открытого API Rutube
(/api/video/<id>/, поле thumbnail_url), ужаты до 720×405
и 22–40 КБ. Всего 17 уникальных роликов и 48 вставок в пяти общих файлах — то есть правка
сделана не в одной странице, а везде, где эти блоки выводятся (37 страниц с видео).
// обработчик один, в подвале шаблона: обложка -> живой плеер
var box = e.target.closest('.rt-facade');
if (!box) return;
var ifr = document.createElement('iframe');
ifr.src = box.getAttribute('data-src') + '?autoStart=true';
ifr.width = '99%';
ifr.height = box.getAttribute('data-h');
box.parentNode.replaceChild(ifr, box);
Чат-виджет, который давал 2,3 секунды блокировки, переведён на загрузку после первого действия посетителя (прокрутка, клик, клавиша) или через пять секунд. Файл виджета не менялся — изменился только момент, когда его скрипты подключаются.
| Показатель | Было | Стало |
|---|---|---|
| Балл производительности | 32–47 | 88–89 |
| LCP | 8,9 с | 3,2 с |
| Первая отрисовка | 3,0 с | 1,4 с |
| TBT | 3 100 мс | 80–170 мс |
| Вес страницы | 1 394 КБ | 442 КБ |
| Запросов | 114 | 38 |
До нажатия на обложку — ни одного iframe и ни одного запроса к плееру. После нажатия появляется iframe нужного размера с адресом ролика и параметром автозапуска, загрузка плеера начинается. То есть видео работает как раньше, просто не грузится заранее.
Кнопка чата на этих страницах не работала и до правок: в консоли падает
TypeError ... setting 'onclick' в config.js, а элемента
.chatbot__btn, на который он ссылается, в разметке нет. Виджет честно
загружался — и ничего не делал. Проверил на исходной версии специально, чтобы не свалить
это на свои правки: ошибка та же. Вывод на будущее: проверять не «скрипт загрузился»,
а «виджет работает».
Что осталось на этой странице: Google Tag Manager (122 КБ, в отчёте показан трижды), рекламный бандл Яндекса (227 КБ) и Метрика. Это чужие счётчики, их откладывают отдельно и с согласия владельца — молча не трогал.
09Что осталось и что не вышло
Не вышло:
- Убрать служебные стили движка. В шапке осталось три файла Битрикса: стили всплывающих окон (6,8 КБ), токены интерфейса (4,7 КБ) и шрифт Open Sans (0,6 КБ). В отчёте они дают 960 мс блокировки. Штатный способ — отфильтровать список подключаемых файлов — привёл к тому, что движок пересобрал все стили в один файл на 1,5 КБ, и страница осталась без оформления. Откатил за пару минут; сайт и сейчас в порядке, но приём повторять без отдельного согласия не буду.
- Убрать полифилы из ядра. 19 КБ кода для старых браузеров и 47 КБ неиспользуемого кода лежат внутри ядра Битрикса. Это пересборка движка — отдельная задача с отдельными рисками.
- Ускорить кэш css и js из файла на диске. Их отдаёт nginx, из
.htaccessэто не меняется — владелец включил годовой кэш в панели хостинга.
Осталось на решение владельца:
- LCP 3,3 секунды. Картинка скачивается за 100 мс, но показ ждёт, пока браузер разберёт тридцать килобайт стилей из шапки. Вынос их в общий файл — последний крупный резерв, и он трогает порядок правил, то есть может повлиять на вид.
- Фон одного из блоков весит 124 КБ. Его можно ужать до семидесяти, но на большом экране он станет мягче. Это уже вопрос вкуса, а не техники.
10Чек-лист, если делать у себя
- Начать с раскрытых пунктов отчёта: там перечислены файлы и их вклад в миллисекундах. Без этого списка правки идут наугад.
- Мерить по три-пять прогонов и сравнивать медиану — одиночный замер на Битриксе ничего не значит.
- Проверить каждый тяжёлый файл внутрь: SVG с вложенной картинкой, шрифт со всеми алфавитами, «минифицированный» файл, который на самом деле не минифицирован.
- Кэш проверять отдельным запросом по каждому типу файла, а не «по расширениям на глаз»: часть статики может отдавать nginx мимо ваших правил.
- Урезая CSS, не вырезать правила компонентов, которыми управляет скрипт. Признак поломки: элементов на странице столько же, ошибок в консоли ноль, а меню не открывается.
- После каждой правки сравнивать геометрию всех элементов страницы с исходной, а не полагаться на снимок: на живом сайте с анимацией и ленивой загрузкой два снимка одного и того же кода расходятся до процента точек.
- Шрифт урезать по символам, а не менять на системный: первое не видно, второе видно всем.
- Проверять кликом: меню, вложенные пункты, выпадающие списки, аккордеоны, модальные окна — на телефоне и на компьютере.
- Чужие плееры выносить в обложку с кнопкой:
loading="lazy"на медленном соединении не спасает, браузер тянет iframe за несколько тысяч пикселей до экрана. - Виджеты (чаты, квизы, отзывы) грузить после первого действия посетителя. И проверять, что виджет после этого работает: «скрипт загрузился» не значит «кнопка нажимается».