Узнайте, что такое сканер предварительной загрузки браузера, как он повышает производительность и как можно ему не мешать.
Один из упускаемых из виду аспектов оптимизации скорости страницы — это знание внутренних компонентов браузера. Браузеры выполняют определенные оптимизации для повышения производительности способами, которые мы, разработчики, не можем сделать, но только до тех пор, пока эти оптимизации не будут непреднамеренно нарушены.
Одна из внутренних оптимизаций браузера, которую нужно понять, — это сканер предварительной загрузки браузера. В этой статье мы рассмотрим, как работает сканер предварительной загрузки, и, что еще важнее, как вы можете не мешать ему.
Что такое сканер предварительной загрузки?
В каждом браузере есть основной HTML-парсер, который токенизирует сырую разметку и преобразует ее в объектную модель . Все это весело продолжается до тех пор, пока парсер не остановится, когда найдет блокирующий ресурс , такой как таблица стилей, загруженная элементом <link>
, или скрипт, загруженный элементом <script>
без атрибута async
или defer
.
<link>
для внешнего файла CSS, который блокирует анализ остальной части документа браузером — или даже его рендеринг — до тех пор, пока CSS не будет загружен и проанализирован.В случае с файлами CSS рендеринг блокируется, чтобы предотвратить появление мелькания нестилизованного содержимого (FOUC) , когда нестилизованная версия страницы отображается на короткое время до того, как к ней будут применены стили.

Браузер также блокирует синтаксический анализ и рендеринг страницы, когда встречает элементы <script>
без атрибута defer
или async
.
Причина этого в том, что браузер не может знать наверняка, изменит ли какой-либо скрипт DOM, пока основной парсер HTML все еще выполняет свою работу. Вот почему стало обычной практикой загружать JavaScript в конце документа, чтобы эффекты заблокированного парсинга и рендеринга стали незначительными.
Это веские причины, по которым браузер должен блокировать как парсинг, так и рендеринг. Тем не менее, блокировка любого из этих важных шагов нежелательна, поскольку они могут задержать шоу, задерживая обнаружение других важных ресурсов. К счастью, браузеры делают все возможное, чтобы смягчить эти проблемы с помощью вторичного HTML-парсера, называемого сканером предварительной загрузки .
<body>
, но сканер предварительной загрузки может заглянуть вперед в необработанную разметку, чтобы найти этот ресурс изображения и начать его загрузку до того, как основной парсер HTML будет разблокирован.Роль сканера предварительной загрузки является спекулятивной , то есть он проверяет сырую разметку, чтобы найти ресурсы для своевременного извлечения до того, как их обнаружит основной HTML-анализатор.
Как определить, работает ли сканер предварительной загрузки
Сканер предварительной загрузки существует из-за заблокированного рендеринга и парсинга. Если бы эти две проблемы производительности никогда не существовали, сканер предварительной загрузки был бы не очень полезен. Ключ к выяснению того, выигрывает ли веб-страница от сканера предварительной загрузки, зависит от этих блокирующих явлений. Для этого можно ввести искусственную задержку для запросов, чтобы узнать, где работает сканер предварительной загрузки.
Возьмем в качестве примера эту страницу с базовым текстом и изображениями с таблицей стилей. Поскольку файлы CSS блокируют как рендеринг, так и парсинг, вы вводите искусственную задержку в две секунды для таблицы стилей через прокси-сервис. Эта задержка облегчает просмотр в сетевом водопаде, где работает сканер предварительной загрузки.

Как вы можете видеть в водопаде, сканер предварительной загрузки обнаруживает элемент <img>
даже при блокировке рендеринга и анализа документа . Без этой оптимизации браузер не может извлекать данные по мере необходимости в течение периода блокировки, и больше запросов ресурсов будут последовательными, а не параллельными.
Разобравшись с этим игрушечным примером, давайте рассмотрим некоторые реальные закономерности, в которых сканер предварительной загрузки может быть обманут, и что можно сделать, чтобы их исправить.
Внедренные async
скрипты
Допустим, у вас в <head>
есть HTML-код, включающий встроенный JavaScript, например:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Внедренные скрипты по умолчанию async
, поэтому при внедрении этого скрипта он будет вести себя так, как будто к нему применен атрибут async
. Это означает, что он будет запущен как можно скорее и не будет блокировать рендеринг. Звучит оптимально, не так ли? Тем не менее, если вы предположите, что этот встроенный <script>
идет после элемента <link>
, который загружает внешний файл CSS, вы получите неоптимальный результат:

async
скрипт. Сканер предварительной загрузки не может обнаружить скрипт во время фазы блокировки рендеринга, поскольку он внедрен на клиенте.Давайте разберем, что здесь произошло:
- На 0 секундах запрашивается основной документ.
- Через 1,4 секунды поступает первый байт навигационного запроса.
- На 2,0 секунде запрашиваются CSS и изображение.
- Поскольку парсер заблокирован при загрузке таблицы стилей, а встроенный JavaScript, внедряющий
async
скрипт, следует за этой таблицей стилей через 2,6 секунды, функциональность, предоставляемая скриптом, не становится доступной так скоро, как могла бы быть.
Это неоптимально, поскольку запрос на скрипт происходит только после завершения загрузки таблицы стилей. Это задерживает запуск скрипта как можно скорее. Напротив, поскольку элемент <img>
обнаруживается в разметке, предоставленной сервером, он обнаруживается сканером предварительной загрузки.
Итак, что произойдет, если вы используете обычный тег <script>
с атрибутом async
вместо внедрения скрипта в DOM?
<script src="/https/web.developers.google.cn/yall.min.js" async></script>
Вот результат:

async
элемент <script>
. Сканер предварительной загрузки обнаруживает скрипт во время фазы блокировки рендеринга и загружает его одновременно с CSS. Может возникнуть соблазн предположить, что эти проблемы можно исправить, используя rel=preload
. Это, конечно, сработает, но может иметь некоторые побочные эффекты. В конце концов, зачем использовать rel=preload
для исправления проблемы, которую можно избежать, не внедряя элемент <script>
в DOM?

async
скрипт, но async
скрипт предварительно загружен, чтобы гарантировать его более раннее обнаружение. Предварительная загрузка «исправляет» проблему здесь, но она вносит новую проблему: async
скрипт в первых двух демонстрациях — несмотря на загрузку в <head>
— загружается с «Низким» приоритетом, тогда как таблица стилей загружается с «Наивысшим» приоритетом. В последней демонстрации, где async
скрипт предварительно загружен, таблица стилей все еще загружается с «Наивысшим» приоритетом, но приоритет скрипта повышен до «Высокого».
Когда приоритет ресурса повышается, браузер выделяет ему больше пропускной способности. Это означает, что — даже если таблица стилей имеет наивысший приоритет — повышенный приоритет скрипта может вызвать конкуренцию за пропускную способность. Это может быть фактором при медленных соединениях или в случаях, когда ресурсы довольно большие.
Ответ здесь прост: если скрипт необходим во время запуска, не побеждайте сканер предварительной загрузки, внедряя его в DOM. Поэкспериментируйте по мере необходимости с размещением элемента <script>
, а также с такими атрибутами, как defer
и async
.
Ленивая загрузка с помощью JavaScript
Ленивая загрузка — отличный метод сохранения данных, который часто применяется к изображениям. Однако иногда ленивая загрузка неправильно применяется к изображениям, которые находятся «выше сгиба», так сказать.
Это приводит к потенциальным проблемам с обнаружением ресурсов, когда речь идет о сканере предварительной загрузки, и может неоправданно задержать время, необходимое для обнаружения ссылки на изображение, его загрузки, декодирования и представления. Давайте возьмем для примера эту разметку изображения:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Использование префикса data-
является распространенным шаблоном в ленивых загрузчиках на основе JavaScript. Когда изображение прокручивается в область просмотра, ленивый загрузчик удаляет префикс data-
, что означает, что в предыдущем примере data-src
становится src
. Это обновление побуждает браузер извлечь ресурс.
Этот шаблон не является проблемным, пока он не применяется к изображениям, которые находятся в области просмотра во время запуска. Поскольку сканер предварительной загрузки не считывает атрибут data-src
так же, как атрибут src
(или srcset
), ссылка на изображение не обнаруживается раньше. Хуже того, загрузка изображения задерживается до тех пор, пока ленивый загрузчик JavaScript не загрузит, не скомпилирует и не выполнит.

В зависимости от размера изображения, который может зависеть от размера области просмотра, оно может быть кандидатом на Largest Contentful Paint (LCP) . Когда сканер предварительной загрузки не может спекулятивно извлечь ресурс изображения заранее — возможно, в момент, когда таблица(ы) стилей страницы блокируют рендеринг — страдает LCP.
Решение — изменить разметку изображения:
<img src="/https/web.developers.google.cn/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Это оптимальный шаблон для изображений, которые находятся в области просмотра во время запуска, поскольку сканер предварительной загрузки быстрее обнаружит и извлечет ресурс изображения.

Результатом в этом упрощенном примере является улучшение LCP на 100 миллисекунд при медленном соединении. Это может показаться не таким уж большим улучшением, но это так, если учесть, что решение — это быстрое исправление разметки, и что большинство веб-страниц сложнее, чем этот набор примеров. Это означает, что кандидатам LCP, возможно, придется бороться за пропускную способность со многими другими ресурсами, поэтому такие оптимизации становятся все более важными.
Фоновые изображения CSS
Помните, что сканер предварительной загрузки браузера сканирует разметку . Он не сканирует другие типы ресурсов, такие как CSS, которые могут включать выборку изображений, на которые ссылается свойство background-image
.
Как и HTML, браузеры обрабатывают CSS в свою собственную объектную модель, известную как CSSOM . Если внешние ресурсы обнаруживаются при построении CSSOM, эти ресурсы запрашиваются во время обнаружения, а не сканером предварительной загрузки.
Допустим, кандидат LCP вашей страницы — это элемент со свойством CSS background-image
. Вот что происходит при загрузке ресурсов:

background-image
(строка 3). Запрашиваемое изображение не начинает извлекаться, пока его не найдет парсер CSS. В этом случае сканер предварительной загрузки не столько побежден, сколько не задействован. Тем не менее, если кандидат LCP на странице взят из CSS-свойства background-image
, вам нужно будет предварительно загрузить это изображение:
<!-- Make sure this is in the <head> below any
stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">
Подсказка rel=preload
невелика, но она помогает браузеру обнаружить изображение раньше, чем это было бы в противном случае:

background-image
(строка 3). Подсказка rel=preload
помогает браузеру обнаружить изображение примерно на 250 миллисекунд раньше, чем без подсказки. С подсказкой rel=preload
кандидат LCP обнаруживается раньше, что сокращает время LCP. Хотя эта подсказка помогает решить эту проблему, лучшим вариантом может быть оценка того, нужно ли загружать кандидат LCP вашего изображения из CSS. С тегом <img>
у вас будет больше контроля над загрузкой изображения, подходящего для области просмотра, при этом позволяя сканеру предварительной загрузки обнаружить его.
Встраивание слишком большого количества ресурсов
Встраивание — это практика, которая помещает ресурс внутрь HTML. Вы можете встраивать таблицы стилей в элементы <style>
, скрипты в элементы <script>
и практически любой другой ресурс, используя кодировку base64 .
Встраивание ресурсов может быть быстрее, чем их загрузка, поскольку для ресурса не выдается отдельный запрос. Он находится прямо в документе и загружается мгновенно. Однако есть существенные недостатки:
- Если вы не кэшируете свой HTML — а вы просто не можете этого сделать, если ответ HTML является динамическим — встроенные ресурсы никогда не кэшируются. Это влияет на производительность, поскольку встроенные ресурсы не подлежат повторному использованию.
- Даже если вы можете кэшировать HTML, встроенные ресурсы не являются общими для всех документов. Это снижает эффективность кэширования по сравнению с внешними файлами, которые можно кэшировать и повторно использовать по всему источнику.
- Если вы встроите слишком много, вы задержите сканер предварительной загрузки в обнаружении ресурсов далее в документе, поскольку загрузка этого дополнительного встроенного контента займет больше времени.
Возьмем в качестве примера эту страницу . При определенных условиях кандидатом LCP является изображение в верхней части страницы, а CSS находится в отдельном файле, загружаемом элементом <link>
. На странице также используются четыре веб-шрифта, которые запрашиваются как отдельные файлы из ресурса CSS.

<img>
, но оно обнаружено сканером предварительной загрузки, поскольку CSS и шрифты, необходимые для страницы, загружаются в отдельных ресурсах, что не задерживает работу сканера предварительной загрузки.А что произойдет, если CSS и все шрифты будут встроены как ресурсы base64?

<img>
, но встраивание CSS и его четырех ресурсов шрифтов в ` ` задерживает обнаружение изображения сканером предварительной загрузки до тех пор, пока эти ресурсы не будут полностью загружены.Влияние встраивания приводит к негативным последствиям для LCP в этом примере — и для производительности в целом. Версия страницы, которая ничего не встраивает, рисует изображение LCP примерно за 3,5 секунды. Страница, которая встраивает все, рисует изображение LCP только через чуть более 7 секунд.
Здесь задействовано больше, чем просто сканер предварительной загрузки. Встраивание шрифтов — не лучшая стратегия, поскольку base64 — неэффективный формат для двоичных ресурсов. Другим фактором является то, что внешние ресурсы шрифтов не загружаются, если только они не определены как необходимые CSSOM. Когда эти шрифты встраиваются как base64, они загружаются независимо от того, нужны ли они для текущей страницы или нет.
Может ли предварительная загрузка улучшить ситуацию? Конечно. Вы можете предварительно загрузить изображение LCP и сократить время LCP, но раздувание потенциально некэшируемого HTML встроенными ресурсами имеет другие негативные последствия для производительности. Первая отрисовка содержимого (FCP) также подвержена влиянию этого шаблона. В версии страницы, где ничего не встроено, FCP составляет примерно 2,7 секунды. В версии, где все встроено, FCP составляет примерно 5,8 секунды.
Будьте очень осторожны со встраиванием чего-либо в HTML, особенно в ресурсах с кодировкой base64. В общем случае это не рекомендуется, за исключением очень маленьких ресурсов. Встраивайте как можно меньше, потому что слишком частое встраивание — это игра с огнем.
Рендеринг разметки с помощью клиентского JavaScript
Нет никаких сомнений: JavaScript определенно влияет на скорость страницы . Разработчики не только полагаются на него для обеспечения интерактивности, но также существует тенденция полагаться на него для доставки самого контента. Это приводит к улучшению опыта разработчика в некотором смысле; но преимущества для разработчиков не всегда трансформируются в преимущества для пользователей.
Одним из шаблонов, который может обойти сканер предварительной загрузки, является рендеринг разметки с помощью клиентского JavaScript:

Когда полезные данные разметки содержатся и полностью визуализируются JavaScript в браузере, любые ресурсы в этой разметке фактически невидимы для сканера предварительной загрузки. Это задерживает обнаружение важных ресурсов, что, безусловно, влияет на LCP. В случае этих примеров запрос изображения LCP значительно задерживается по сравнению с эквивалентным опытом, визуализированным сервером, который не требует появления JavaScript.
Это немного отклоняется от фокуса этой статьи, но эффекты от рендеринга разметки на клиенте выходят далеко за рамки победы над сканером предварительной загрузки. Во-первых, внедрение JavaScript для обеспечения опыта, который не требует этого, вводит ненужное время обработки, которое может повлиять на взаимодействие со следующей отрисовкой (INP) . Рендеринг чрезвычайно больших объемов разметки на клиенте с большей вероятностью приведет к созданию длительных задач по сравнению с тем же объемом разметки, отправляемым сервером. Причина этого — помимо дополнительной обработки, которую включает JavaScript — заключается в том, что браузеры передают потоковую разметку с сервера и разбивают рендеринг таким образом, что это имеет тенденцию ограничивать длительные задачи. С другой стороны, разметка, отрисованная клиентом, обрабатывается как одна монолитная задача, что может повлиять на INP страницы.
Средство для этого сценария зависит от ответа на этот вопрос: есть ли причина, по которой разметка вашей страницы не может быть предоставлена сервером, а не отображена на клиенте? Если ответ на этот вопрос «нет», следует рассмотреть возможность рендеринга на стороне сервера (SSR) или статически сгенерированной разметки, где это возможно, поскольку это поможет сканеру предварительной загрузки обнаружить и своевременно извлечь важные ресурсы заранее.
Если вашей странице требуется JavaScript для добавления функциональности к некоторым частям разметки страницы, вы все равно можете сделать это с помощью SSR, либо с помощью обычного JavaScript, либо с помощью гидратации, чтобы получить лучшее из обоих миров.
Помогите сканеру предварительной загрузки помочь вам
Сканер предварительной загрузки — это высокоэффективная оптимизация браузера, которая помогает страницам загружаться быстрее во время запуска. Избегая шаблонов, которые мешают ему обнаруживать важные ресурсы заранее, вы не просто упрощаете разработку для себя, вы создаете лучший пользовательский опыт, который обеспечит лучшие результаты по многим показателям, включая некоторые веб-показатели .
Подводя итог, вот что вам следует вынести из этой статьи:
- Сканер предварительной загрузки браузера — это вторичный HTML-анализатор, который выполняет сканирование раньше основного, если он заблокирован, чтобы по возможности обнаружить ресурсы, которые он может получить раньше.
- Ресурсы, которые отсутствуют в разметке, предоставленной сервером в начальном запросе навигации, не могут быть обнаружены сканером предварительной загрузки. Способы, которыми можно обойти сканер предварительной загрузки, могут включать (но не ограничиваются):
- Внедрение ресурсов в DOM с помощью JavaScript, будь то скрипты, изображения, таблицы стилей или что-либо еще, что было бы лучше в первоначальной полезной нагрузке разметки с сервера.
- Ленивая загрузка изображений в верхней части страницы или фреймов с использованием решения JavaScript.
- Отображение разметки на клиенте, которая может содержать ссылки на подресурсы документа с использованием JavaScript.
- Сканер предварительной загрузки сканирует только HTML. Он не проверяет содержимое других ресурсов, в частности CSS, которые могут включать ссылки на важные активы, включая кандидатов LCP.
Если по какой-либо причине вы не можете избежать шаблона, который негативно влияет на способность сканера предварительной загрузки ускорять производительность загрузки, рассмотрите подсказку ресурса rel=preload
. Если вы используете rel=preload
, протестируйте в лабораторных инструментах, чтобы убедиться, что он дает вам желаемый эффект. Наконец, не загружайте слишком много ресурсов предварительно, потому что когда вы расставляете приоритеты по всему, ничего не будет.
Ресурсы
- «Асинхронные скрипты», внедренные в скрипт, считаются вредоносными
- Как предварительный загрузчик браузера ускоряет загрузку страниц
- Предварительно загрузите критически важные ресурсы для повышения скорости загрузки
- Устанавливайте сетевые соединения заранее, чтобы улучшить воспринимаемую скорость загрузки страниц
- Оптимизация отрисовки самого большого содержимого
Главное изображение с сайта Unsplash , автор Мохаммад Рахмани .