Страница, которая на машине разработчика открывается мгновенно, в реальной сети часто теряет первые сотни миллисекунд ещё до того, как браузер успеет нарисовать хоть один символ. Причина редко в тяжёлых картинках и почти никогда в медленном сервере. Время съедает невидимая работа по установке соединений с чужими доменами, откуда приходят шрифты, счётчики аналитики и статические файлы. Атрибуты-подсказки preconnect и dns-prefetch заставляют браузер заняться этой работой заранее, пока разметка ещё разбирается. Ниже разбирается, как устроены оба механизма, сколько миллисекунд они возвращают на шрифтах и аналитике и в какой момент избыток подсказок начинает вредить скорости.
Что браузер делает перед первым запросом к чужому домену
Разметка страницы почти всегда ссылается минимум на несколько доменов. Стили и шрифты лежат на одном хосте, картинки на другом, счётчик аналитики на третьем. Когда парсер встречает адрес, которого ещё нет в кэше, начинается цепочка обязательных шагов. Сначала доменное имя превращается в IP-адрес, на это уходит от 20 до 120 миллисекунд, а на мобильных сетях дольше. Затем открывается TCP-соединение, один круговой обход до сервера и обратно. После этого согласовывается шифрование TLS, ещё один обход для TLS 1.3 и два для более старой версии 1.2. Итого перед первым байтом любого файла набегают три-четыре круговых обхода плюс резолвинг домена.
Проблема в том, что браузер не может начать всё это заранее по собственной инициативе. Он узнаёт адрес шрифтового хоста только тогда, когда дочитал разметку до соответствующей строки или получил CSS, внутри которого спрятан адрес файла woff2. Пока идёт эта цепочка, текст на экране либо отсутствует, либо отображается запасным шрифтом и затем заметно прыгает. Подсказки в блоке head сообщают браузеру имена доменов в самом начале, и вся подготовка соединений уходит параллельно разбору HTML вместо последовательного ожидания.
Три уровня подсказок от резолвинга домена до загрузки самого файла
Механизмы прогрева образуют лестницу из трёх ступеней, и у каждой своя цена и свой эффект. Первая ступень, dns-prefetch, только превращает доменное имя в IP-адрес. Это самая дешёвая операция, она экономит несколько десятков миллисекунд и почти ничего не стоит. Вторая ступень, preconnect, прогревает соединение целиком: резолвинг домена, TCP-рукопожатие и согласование TLS. Браузер держит готовый канал и при реальном запросе отправляет файл сразу, без паузы на подготовку. Третья ступень, preload, скачивает конкретный файл с повышенным приоритетом, для неё нужен точный адрес ресурса. Выглядят подсказки так:
<link rel="dns-prefetch" href="https://mc.yandex.ru">
<link rel="preconnect" href="https://mc.yandex.ru">
<link rel="preload" href="https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2" as="font" type="font/woff2" crossorigin>
Preconnect включает в себя и DNS, поэтому отдельный dns-prefetch для того же домена скорость не добавляет, он служит страховкой для старых браузеров. Preload не заменяет preconnect для аналитики, потому что точный адрес скрипта счётчика заранее неизвестен, а главное узкое место там именно в установке соединения, а не в размере файла. Шрифтовые файлы, напротив, известны заранее, поэтому для них связка preload плюс preconnect даёт максимальный эффект.
У preload есть собственные требования аккуратности. Атрибут as сообщает браузеру тип ресурса и правильный приоритет, для шрифтов используется значение font, а type с указанием формата позволяет пропустить загрузку, если браузер этот формат не поддерживает. Preload шрифта без атрибута crossorigin повторяет классическую ошибку preconnect: файл скачается дважды, один раз в обход CORS и второй раз по настоящему запросу из CSS. Злоупотреблять preload тоже не следует: каждый преждевременно скачанный файл занимает канал, и если на первом экране он не нужен, страница теряет больше, чем приобретает.
Подключение шрифтов со сторонних доменов через preconnect с crossorigin
Шрифты, подключаемые через популярные сервисы вроде Google Fonts, живут на двух доменах. CSS-файл описания приходит с fonts.googleapis.com, а сами файлы woff2 отдаются с fonts.gstatic.com. Для прогрева нужны оба, и здесь прячется самая частая ошибка. Шрифтовые файлы браузер запрашивает в CORS-режиме, поэтому у preconnect для fonts.gstatic.com обязан стоять атрибут crossorigin. Без него браузер прогревает одно соединение, а при реальном запросе шрифта открывает второе, с другим набором заголовков, и весь прогрев уходит впустую. Рабочий блок выглядит так:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
Для CSS-файла crossorigin не нужен, потому что таблица стилей запрашивается в обычном режиме. Дублирующие строки dns-prefetch не ускоряют современные браузеры, зато подхватываются старыми версиями, которые атрибут preconnect ещё не понимают, включая Internet Explorer и ранние Safari. Экономия получается двойная: заголовок CSS приходит раньше на 100-200 миллисекунд, а сам шрифт, самое узкое место отрисовки текста, на мобильном соединении выигрывает 200-400 миллисекунд только за счёт заранее открытого канала.
Отдельное внимание стоит уделить точности адреса. Подсказка срабатывает только при полном совпадении источника вплоть до протокола и поддомена. Прогрев адреса вида example.com при реальном запросе к www.example.com браузер игнорирует, и соединение открывается заново. То же правило действует для протокола: страница по https не может переиспользовать прогрев по http.
Ускорение аналитических счётчиков и сторонних скриптов
Скрипты аналитики почти никогда не блокируют отрисовку, поэтому кажется, что скорость их загрузки не важна. На деле у запаздывающего счётчика есть измеримая цена: событие о визите уходит позже, а часть посетителей успевает закрыть вкладку до отправки, и статистика теряет быстрые отказы. Скрипт счётчика обычно внедряется динамически, и сканер предварительной загрузки браузера, который заранее собирает адреса из разметки, его просто не видит. Соединение с доменом аналитики начинается только после выполнения части JavaScript, когда самый ценный отрезок времени уже упущен. Подсказка preconnect, поставленная в head, запускает прогрев соединения в момент старта разбора, параллельно с загрузкой стилей и первых изображений.
Числовой пример. Скрипт счётчика весит 30-50 килобайт в сжатом виде, на мобильном 4G его загрузка занимает 100-200 миллисекунд, а установка соединения с нуля добавляет ещё 150-300. С preconnect канал уже готов к моменту, когда скрипт запускается, и событие о визите уходит на 150-300 миллисекунд раньше. Для аналитики это означает более точный учёт первых секунд на странице, для сторонних чатов и виджетов обратной связи заметно более быстрое появление на экране. Тот же приём работает для менеджеров тегов, которые тянут десятки сторонних скриптов с одного хоста.
Сколько миллисекунд реально экономит прогрев соединений
Оценка зависит от сети. Разбор DNS даёт 20-120 миллисекунд, его покрывает даже дешёвый dns-prefetch. Полный preconnect добавляет к этому TCP-рукопожатие и TLS, то есть один-три круговых обхода. На настольном интернете с задержкой 20-40 миллисекунд выигрыш на один домен составляет 80-160 миллисекунд. На мобильных сетях с обходом 50-150 миллисекунд прогрев одного домена возвращает 150-500 миллисекунд, потому что каждый круговой обход там обходится дорого. Возьмём страницу интернет-магазина с задержкой сети 80 миллисекунд: DNS стоит 60 миллисекунд, TCP ещё 80, TLS ещё 80, итого 220 миллисекунд на каждый домен. Три прогретых домена, шрифты, счётчик и CDN статики, суммарно возвращают до 600 миллисекунд, и всё это время приходится на самый важный отрезок загрузки, до первой отрисовки.
Для метрик это оборачивается конкретными сдвигами. Шрифт, прибывший раньше, сокращает показатель LCP на 100-300 миллисекунд, потому что текст первого экрана перестаёт ждать цепочку CSS и рукопожатий. Переход серверов на TLS 1.3 укорачивает рукопожатие с двух обходов до одного, и preconnect автоматически наследует это ускорение. В протоколе HTTP/2 одно соединение мультиплексирует множество запросов, но чужой домен всё равно требует собственного рукопожатия, поэтому прогрев междоменных источников сохраняет ценность. Отдельный бонус получают повторные переходы внутри сайта: соединение, прогретое на первой странице, остаётся живым, и внутренние страницы открываются быстрее без дополнительных настроек.
Протокол HTTP/3 меняет расклад частично. QUIC объединяет транспортное рукопожатие и согласование шифрования в один обход, а при повторных визитах восстанавливает соединение вообще без ожидания. Прогрев на повторных загрузках дешевеет, однако резолвинг домена никуда не исчезает, а первая загрузка стороннего ресурса по-прежнему стоит минимум одного кругового обхода до далёкого дата-центра. Поэтому подсказки остаются полезными и на современных серверах, просто выигрыш становится скромнее и сильнее зависит от географии аудитории: чем дальше посетитель от сервера, тем дороже каждый обход и тем заметнее вклад заранее открытого канала.
Подводные камни лишних подсказок и лимит шести соединений на домен
У прогрева есть обратная сторона, и она проявляется ровно тогда, когда подсказками злоупотребляют. Открытый preconnect держит сокет занятым около десяти секунд, а браузеры в протоколе HTTP/1.1 открывают не больше шести параллельных соединений на один домен. Если прогреть пять-семь доменов впрок, часть из них окажется невостребованной: страница не запросит с этих хостов ничего критичного, а сокеты будут заняты. Соединения, простаивающие без дела, нагружают сервер лишними рукопожатиями, расходуют батарею смартфона и место в ограниченном пуле браузера. Хуже того, лишние открытые каналы конкурируют с критичными запросами за те самые шесть слотов, и загрузка стилей замедляется вместо ускорения.
Прогревают два-четыре домена, без которых первая отрисовка действительно буксует. Разумный набор для типовой страницы ограничен коротким перечнем:
- хост файлов шрифтов, отдающий woff2 для текста первого экрана, например fonts.gstatic.com с атрибутом crossorigin;
- CDN статики, если критичный CSS или JavaScript лежит на отдельном домене;
- хост аналитического счётчика, без которого статистика теряет первые секунды визита;
- домен ключевых изображений, если баннер первого экрана отдаётся с отдельного хоста.
Всё, что остаётся за пределами этого перечня, получает dns-prefetch или не получает ничего. Список доменов стоит пересматривать после каждого изменения подключений: прогрев адреса, который больше не используется, чистый вред без единой выгоды. Ещё одна тонкость касается размещения: строки подсказок ставят в самое начало head, потому что каждая миллисекунда до их обнаружения превращается в прямую задержку прогрева, а если реальный запрос случается позже десяти секунд, браузер успевает закрыть сокет и прогрев сгорает.
Порядок внедрения и проверка результата в браузере
Внедрение начинается с аудита, а не с копирования готовых строк. В инструментах разработчика открывается вкладка Network, страница перезагружается с пустым кэшем, и по столбцу Domain собираются все сторонние хосты, задействованные в первой отрисовке. Для двух-четырёх самых важных добавляются строки preconnect в head, для каждого из них дублёр dns-prefetch. Проверка результата занимает минуту: в той же вкладке Network открывается запрос шрифта, и на вкладке Timing видно, что фазы DNS, TCP и SSL практически пустые, соединение переиспользовано. Если фазы заполнены, потерян атрибут crossorigin или адрес указан не с тем поддоменом.
Дальше в дело вступает Lighthouse. Аудит Preconnect to required origins прямо называет домены, по которым прогрев не выполнен, а раздел метрик показывает сдвиг LCP и FCP. Замерять стоит до внедрения и после, на троттлинге медленного 4G, чтобы числа отражали мобильную реальность, а не локальный комфорт. Связка с preload закрывает картину для шрифтов: preconnect готовит канал, preload тянет сам файл woff2 с высоким приоритетом, и текст первого экрана перестаёт мерцать подстановочным шрифтом. Итог аккуратной настройки выглядит скромно в коде, четыре-шесть строк в head, и ощутимо в метриках, несколько сотен миллисекунд, отвоеванных у сети до того, как посетитель успеет заметить ожидание.