О скорости собственного сайта мы писали дважды. Сначала — почему страница думала три секунды: тяжелый запрос меню, выполнявшийся при каждом показе. Потом — сжатие, которое будто бы работало; во второй половине той статьи показано, что вес современной страницы сидит в сериализованных данных для компонентов, а не в разметке. Этот тезис здесь не повторяем.
Вопрос этой статьи другой: что именно едет в браузер вместе с каждой страницей, хотя посетитель этим никогда не воспользуется. 5 сентября 2026 мы разобрали ответ собственного сайта и нашли пять таких пассажиров. 13 сентября нашелся шестой. 16 и 17 сентября проверили, что из этого осталось, — и увидели, что один пассажир уже понемногу возвращается.
Что нашли 5 сентября
В коммитах того дня записано: на страницу приходилось 300–380 КБ данных React, и самые крупные куски не имели отношения к самой странице.
Словарь целого языка. Все переводы интерфейса ехали в браузер на каждой странице, хотя компоненты, работающие в браузере, читают 19 разделов словаря из 40. Остальное — тексты «О нас», юридических страниц, служебные подписи — переводит сервер, и браузеру они не нужны.
Вторая шапка. Фреймворк кладет в каждый ответ заготовку страницы 404 — на случай, если адрес не найдется во время перехода. Наша страница 404 рисовала полную шапку с меню услуг, так что на каждую страницу ложились еще 18 КБ, которых никто никогда не видел.
Текст статьи трижды. Оглавление статьи собирали два компонента в браузере, и каждый получал весь HTML статьи, чтобы найти в нем заголовки. Текст приезжал один раз для чтения и еще дважды ради списка из нескольких пунктов.
Мобильное меню на компьютере. Содержимое мобильного меню — кнопки направлений, категории, поиск, 29 иконок — занимало 17 КБ разметки и около 150 узлов DOM. Сервер рисовал его на каждой странице и прятал прозрачностью, в том числе тем, кто открыл сайт с компьютера, где это меню не показывается вообще.
Обложка, о которой браузер узнавал поздно. Один прогон трассировки статьи на мобильном вьюпорте (Slow 4G, процессор замедлен в четыре раза) дал самый крупный элемент первого экрана за 1 094 мс. Из них 184 мс браузер просто не знал об обложке: она была фоном в стилях и обнаруживалась лишь после того, как стили загрузились. Еще 574 мс уходило на оригинальный JPEG 1200×630 весом 44–85 КБ, без всякого преобразования под экран.
Шестой пассажир, 13 сентября. Скрипт нашего чата — 57 КБ плюс настройки и открытое соединение — вставлялся сам при первом взаимодействии со страницей или когда она просто стояла. На практике его получал каждый посетитель, а не тот, кто нажал на чат.
Почему этого никто не заметил
Ни одного из этих пассажиров не видно. Словарь не показывается, заготовка 404 не рисуется, пока не понадобится, мобильное меню прозрачное, чат выглядит как кнопка. Страница при этом выглядит правильно и работает правильно, а лишняя работа, которая работает правильно, сама о себе не сообщает. Тот же узор, что и с тяжелым запросом меню в первой статье, только с другой стороны: там невидимое сидело на сервере, здесь — в ответе.
Вторая причина — способ, которым это накапливается. Никто не решал везти словарь целого языка: его подключили один раз на весь сайт, когда словарь был маленьким, и дальше он рос вместе с сайтом. Заготовку 404 ставит фреймворк, а шапку в нее положили, потому что так выглядит обычная страница сайта. Каждое решение по отдельности разумно, а вместе получается багаж.
Что изменили
- В браузер едут только те 19 разделов словаря, которые читают его компоненты. Перечень сверяет отдельный скрипт: он идет по графу импортов от файлов с
'use client' и падает, если раздела не хватает. Без такой проверки пропущенный раздел виден только на живом сайте — ключом перевода вместо текста.
- Страница 404 берет легкую шапку без меню.
- Пункты оглавления статьи собирает сервер, в пропсы идет короткий массив заголовков.
- Содержимое мобильного меню монтируется после первого открытия. В серверной разметке статьи осталось 2 узла вместо 156, а ссылки для поисковых роботов стоят в панели меню для ПК, которая в HTML осталась.
- Обложка стала обычным изображением с предварительной загрузкой и форматами под ширину экрана.
- Чат загружается после клика по кнопке.
Что видим на живом сайте
16 сентября мы сняли три страницы прода с заголовком Accept-Encoding, как в Chrome, и разобрали их одним скриптом.
Шапка внутри заготовки 404 занимает теперь 137 байтов с пустым перечнем услуг — против 23 707 байтов настоящего меню на той же странице. Контейнер мобильного меню в разметке статьи пуст. Скрипт чата в коде страницы не упоминается вообще: вместо него кнопка на 707 байтов. Сам скрипт 17 сентября весил 56 953 байта, по сети сжатым 20 214, — но получает его лишь тот, кто нажал.
А вот словарь вернулся к прежнему размеру, и здесь есть отдельный урок.
Урок о единицах измерения
5 сентября в коммите записано «64 КБ». Когда 16-го мы пересчитали тот же файл словаря, вышло 63 261 символ — и 96 372 байта. Кириллица в UTF-8 занимает два байта на букву, так что записанная тогда цифра описывала символы, а не то, что действительно едет по сети.
В байтах картина такая. Полный словарь украинского на 5 сентября — 96 372 Б в 40 разделах. Девятнадцать нужных разделов после исправления — 57 976 Б. Те же девятнадцать разделов в текущей ветке — 67 924 Б. За одиннадцать дней словарь вырос, потому что на главной появились новые блоки: раздел Home увеличился с 12 852 до 20 539 байтов, 39 новых ключей, самый крупный из них — блок вопросов и ответов на 4 339 байтов.
Этот раздел приезжает на каждую страницу, потому что его читают компоненты главной. В ответе страницы статьи мы проверили все 53 строки из него длиннее 40 символов: ни одна не встречается больше нигде в этом ответе. То есть читатель статьи получает 20,5 КБ текстов главной, которых на его экране нет. После brotli это 5 553 байта против 16 510 на все девятнадцать разделов; числа считали для каждого куска отдельно, в составе страницы они будут немного другими.
Как проверить это у себя
1. Сколько данных везет страница. Для сайтов на Next.js с App Router откройте страницу, консоль DevTools и выполните:
const d = self.__next_f.map(x => x[1] || '').join('');
new TextEncoder().encode(d).length
Это байты. d.length даст символы, и на кириллическом сайте вы недосчитаетесь примерно трети — ровно так, как мы 5 сентября. На Next.js с Pages Router (без App Router) ищите блок <script id="__NEXT_DATA__"> вместо self.__next_f. В других фреймворках ищите в коде страницы крупные блоки <script type="application/json">, которые фреймворк вставляет для гидратации.
2. Не едет ли чужой текст. Скопируйте предложение, которое есть только на главной, и поищите его в коде страницы статьи или товара (Ctrl+U, затем Ctrl+F). Если оно там есть, а на экране нет, это пассажир. Так же ищите тексты попапов, полные списки городов, справочники, переводы других языков.
3. Не рисуется ли скрытое. В инспекторе найдите мобильное меню, модальные окна, корзину — на компьютерной версии сайта. Сотни узлов еще до первого открытия означают, что эти узлы строятся на сервере и передаются каждому.
4. Что загружается до клика. Вкладка Network, перезагрузите страницу, немного пролистайте и ничего не нажимайте. Скрипты чатов, видеоплееров и карт, появившиеся в списке, получает каждый посетитель.
5. Чем является самый крупный элемент первого экрана. Во вкладке Performance найдите LCP. Если это картинка, заданная через background-image в стилях, браузер узнает о ней поздно — именно на этом мы потеряли 184 мс.
Что это не исправило
Словарь растет вместе с сайтом, и разовая обрезка не держится сама. Чтобы тексты главной не ехали на статьи, словарь пришлось бы делить по типам страниц, а в нашей версии библиотеки переводов вложенный провайдер не сливает разделы с родительским — каждому типу пришлось бы везти еще и общие разделы. Мы этого пока не делали.
Шапка с деревом меню услуг по-прежнему занимает 23 707 байтов в данных каждой страницы. Это меню настоящее, им пользуются, и убирать его нет оснований.
Время появления обложки после изменения мы отдельно не замеряли, поэтому цифры «после» здесь не будет. Известно лишь, что теперь браузер узнает о ней с самого начала документа, со строки предварительной загрузки. Да и цифры «до» — 1 094 мс и 184 мс — это один запуск трассировки 5 сентября, а не медиана нескольких прогонов: на искусственно замедленных сети и процессоре такой замер гуляет от раза к разу.
У чата есть осознанная цена: автоматическое открытие через 25 секунд или на намерение уйти со страницы больше не работает, потому что до клика движка чата на странице нет.
И граница всего разбора. Эти килобайты весят на медленном мобильном интернете и на слабых телефонах, где их еще нужно разобрать; на быстром соединении разница почти незаметна. Замеры сделаны на одном сайте, на трех типах страниц, в два конкретных дня — это наш случай, а не норматив. Если заниматься этим самим нет времени, мы делаем ускорение сайта и отдельно мобильную скорость с такими же замерами до и после.