Быстрая загрузка
Что происходит в первые две секунды, какие ресурсы блокируют показ страницы и как отдать содержимое раньше, чем загрузится всё остальное.
Что ты научишься делать
- Разберёшь, какие ресурсы задерживают первую отрисовку
- Подключишь шрифты так, чтобы текст был виден сразу
- Уберёшь сдвиг вёрстки при загрузке картинок
#Две секунды, которые решают всё
Человек не ждёт загрузки — он ждёт содержимого. Если через две секунды на экране белый лист, половина уходит, не дождавшись.
Важна не общая скорость, а момент, когда появилось первое осмысленное. Страница может догружать картинки ещё пять секунд — если текст уже виден, это никого не раздражает.
#Что блокирует показ
Браузер разбирает разметку сверху вниз и по дороге натыкается на ссылки: стили, скрипты, картинки. Некоторые из них останавливают процесс до полной загрузки файла, другие — нет.
| Ресурс | Блокирует ли | Почему |
|---|---|---|
<link rel="stylesheet"> | Да, отрисовку | Браузер не станет показывать неоформленный текст |
<script> без атрибутов | Да, разбор разметки | Скрипт может дописать что угодно в документ |
<script defer> | Нет | Скачивается параллельно, выполняется после разбора |
<script async> | Может | Выполняется сразу по готовности, в непредсказуемый момент |
| Шрифт | Показ текста | По умолчанию браузер прячет текст, пока шрифт грузится |
<img> | Нет | Но без размеров вызывает сдвиг вёрстки |
#Критический CSS
Файл стилей блокирует отрисовку целиком, хотя для первого экрана нужна малая его часть.
Приём: стили первого экрана встроить прямо в head, а основной файл подключить так, чтобы он не блокировал.
<head> <style> /* Только то, что видно без прокрутки: шапка, герой, типографика. Обычно укладывается в 5–10 килобайт */ body { margin: 0; font-family: system-ui, sans-serif; } .header { display: flex; padding: 16px; } .hero { padding: 48px 16px; } </style> <!-- Основной файл не блокирует: грузится как «печатный», а по готовности переключается на обычный --> <link rel="stylesheet" href="main.css" media="print" onload="this.media='all'"> <noscript><link rel="stylesheet" href="main.css"></noscript></head>#Шрифты: самая частая потеря секунды
По умолчанию браузер ждёт загрузки шрифта и не показывает текст до трёх секунд. Это называется невидимым текстом, и это худшее, что можно сделать с первым экраном.
font-display: swap меняет поведение: текст рисуется запасным шрифтом сразу, а потом подменяется.
@font-face { font-family: "Onest"; src: url("/fonts/onest-var.woff2") format("woff2-variations"); font-weight: 100 900; /* Текст виден сразу, шрифт подменится при готовности */ font-display: swap; /* Только нужные символы: кириллица и латиница. Полный набор с иероглифами весит в разы больше */ unicode-range: U+0000-00FF, U+0400-04FF;}:root { /* Запасной шрифт подогнан по метрикам — подмена почти незаметна */ --font-text: "Onest", "Segoe UI", system-ui, sans-serif;}При подмене шрифта текст слегка прыгает: у запасного другие пропорции. Это лечится подгонкой метрик через дескрипторы size-adjust, ascent-override и descent-override — они подгоняют системный шрифт под пропорции основного.
@font-face { font-family: "Onest Fallback"; src: local("Segoe UI"), local("Helvetica Neue"); /* Подгоняем размер, чтобы подмена не двигала строки */ size-adjust: 97%; ascent-override: 92%; descent-override: 24%;}:root { --font-text: "Onest", "Onest Fallback", sans-serif;}#preconnect и preload
Две подсказки браузеру, которые часто путают.
preconnect — «мы точно пойдём на этот домен, установи соединение заранее». Экономит сотни миллисекунд на рукопожатии.
preload — «этот конкретный файл понадобится немедленно, начни качать сейчас». Нужен для ресурсов, о которых браузер узнаёт поздно: шрифт, упомянутый внутри CSS.
<!-- Соединение с доменом шрифтов устанавливается заранее --><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Шрифт упомянут внутри CSS — браузер узнал бы о нём поздно. crossorigin обязателен даже для своего домена --><link rel="preload" href="/fonts/onest-var.woff2" as="font" type="font/woff2" crossorigin>#Картинки и сдвиг вёрстки
Сдвиг вёрстки — когда содержимое прыгает при загрузке. Человек начинает читать, и текст уезжает вниз, потому что сверху догрузилась картинка.
Лечится тем, что мы разбирали в пятом уроке основ: атрибутами width и height. Браузер вычисляет пропорцию и резервирует место заранее.
<!-- Первый экран: грузим сразу и с высоким приоритетом --><img src="hero.webp" alt="Бариста наливает молоко в чашку" width="1200" height="800" fetchpriority="high" decoding="async"><!-- Ниже сгиба: по мере прокрутки --><img src="gallery-3.webp" alt="Витрина" width="600" height="400" loading="lazy" decoding="async">#Чем мерить
Три показателя описывают загрузку с точки зрения человека, а не сервера. Они и лежат в основе оценок поисковиков.
| Показатель | Что означает | Хорошо |
|---|---|---|
| LCP — отрисовка главного элемента | Когда появился самый крупный блок первого экрана: заголовок или картинка | До 2,5 секунды |
| CLS — сдвиг вёрстки | Насколько содержимое прыгало при загрузке | Меньше 0,1 |
| INP — отклик на действие | Через сколько страница ответила на нажатие | До 200 миллисекунд |
Замерять их можно во вкладке Lighthouse. Но лабораторный замер на быстром ноутбуке всегда оптимистичнее реальности: включай замедление сети и процессора, иначе картина будет обманчиво хорошей.
#Как это выглядит в реальном проекте
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 1. Соединения — самыми первыми --> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- 2. Критические ресурсы --> <link rel="preload" href="/fonts/onest-var.woff2" as="font" type="font/woff2" crossorigin> <!-- 3. Стили первого экрана — встроены --> <style>/* … критический CSS … */</style> <!-- 4. Остальные стили — не блокируют --> <link rel="stylesheet" href="/styles/main.css" media="print" onload="this.media='all'"> <noscript><link rel="stylesheet" href="/styles/main.css"></noscript> <!-- 5. Скрипты — с defer --> <script type="module" src="/js/app.js" defer></script> <title>Кофейня «Полка»</title></head>#Когда НЕ надо
- Выносить критический CSS на маленьком сайте. Если весь файл стилей весит пятнадцать килобайт, он загрузится быстрее, чем окупится усложнение.
- Предзагружать всё подряд. Приоритет — ресурс ограниченный: подняв один файл, ты опустил остальные.
- Ставить
lazyвсем картинкам. На первом экране это прямой вред. - Оптимизировать по чужим цифрам. Показатели зависят от аудитории: медленный мобильный интернет и офисный оптоволоконный дают разную картину. Смотри на своих пользователей.
#Поддержка браузерами
| Возможность | Состояние |
|---|---|
font-display | Во всех современных браузерах |
preconnect, preload | Во всех современных браузерах |
loading="lazy" | Во всех современных браузерах |
fetchpriority | Поддерживается в большинстве; где нет — просто игнорируется |
size-adjust и переопределение метрик | Во всех современных браузерах |
decoding="async" | Во всех современных браузерах |
Где спотыкаются почти все
Шрифт без font-display
Почему Браузер прячет текст до трёх секунд. Человек видит пустой макет вместо содержимого.
Как надо Всегда пиши font-display: swap.
preload без crossorigin у шрифта
Почему Шрифты запрашиваются в анонимном режиме. Без атрибута предзагруженный файл не совпадёт с настоящим запросом, и он скачается дважды.
Как надо У шрифтов crossorigin обязателен, даже на своём домене.
Скрипт без defer в head
Почему Разбор разметки останавливается до полной загрузки и выполнения скрипта.
Как надо Добавь defer. Для модулей он подразумевается по умолчанию.
Картинки без width и height
Почему Место не резервируется, содержимое прыгает при загрузке.
Как надо Указывай настоящие размеры файла, а масштабируй в CSS.
- Первая отрисовка First Contentful Paint
- Момент, когда на экране появилось первое содержимое.
- Сдвиг вёрстки Cumulative Layout Shift
- Насколько содержимое прыгало при загрузке.
- Блокирующий ресурс render-blocking
- Файл, из-за которого браузер откладывает показ страницы.
- Критический CSS critical CSS
- Стили первого экрана, встроенные прямо в разметку.
- Невидимый текст FOIT
- Период, когда текст не показан, потому что шрифт ещё грузится.
Проверь себя
Это и называется невидимым текстом. Человек видит пустой макет вместо содержимого.
<code>async</code> выполняется в непредсказуемый момент и может не найти нужный элемент.
Предзагрузка должна совпасть с настоящим запросом по режиму, иначе браузер посчитает их разными.
На первом экране ленивая загрузка только задерживает появление картинки.
Приём держится на обработчике <code>onload</code>. Запасная ссылка обязательна.
Задание
Ускорить страницу
Четыре проблемы: скрипт блокирует разбор, у шрифта нет font-display, нет предварительного соединения с доменом шрифтов, а у картинки первого экрана не заданы размеры и стоит ленивая загрузка.
Почини всё во вкладке HTML.
Подсказка 1
Скрипт: <script src="/js/app.js" defer></script>
Подсказка 2
Соединение: <link rel="preconnect" href="https://fonts.example" crossorigin> — первой строкой.
Подсказка 3
В @font-face добавь строку font-display: swap;
Подсказка 4
Картинке верни размеры: width="1200" height="800" и убери loading="lazy" — она на первом экране.
Показать решение
<link rel="preconnect" href="https://fonts.example" crossorigin><link rel="stylesheet" href="https://fonts.example/onest.css"><script src="/js/app.js" defer></script><style> @font-face { font-family: "Onest"; src: url("/fonts/onest.woff2") format("woff2"); font-display: swap; }</style><h1>Кофейня «Полка»</h1><img class="hero" src="hero.webp" alt="Бариста за стойкой" width="1200" height="800" fetchpriority="high" decoding="async"><p>Варим кофе с восьми утра.</p>body { font-family: "Onest", system-ui, sans-serif; }.hero { width: 100%; height: auto; border-radius: 12px; }