Критический CSS и defer-загрузка: где приёмы ломаются
Критический CSS обещает быстрый первый экран, но чаще ломает вёрстку и мигает нестилизованным контентом. Разбираем четыре типичные ошибки и цену каждого подхода к отложенной загрузке стилей.

Идея выглядит просто: вынести стили первого экрана в <style> внутри <head>, а основную таблицу стилей подгрузить асинхронно. Lighthouse перестаёт ругаться на «Eliminate render-blocking resources», метрика LCP падает. На практике вместо ускорения получают мигание нестилизованного текста (FOUC), поехавшую вёрстку на втором экране и устаревший критический CSS, который забыли пересобрать после релиза. Разберём, где именно ломаются эти приёмы и чем платит каждый вариант отложенной загрузки.
Материал для тех, кто уже настраивал загрузку стилей и упёрся в конкретную проблему: FOUC, скачок layout при подгрузке основного CSS или невозможность поддерживать критический CSS вручную. Инвалидация кеша критического CSS при деплое — смежная тема, здесь её не разворачиваем.
Что вообще считается критическим CSS
Критический CSS1 — это минимальный набор правил, нужных для отрисовки контента, который виден без прокрутки (above the fold). Всё остальное грузится отдельно и не блокирует первую отрисовку.
Критический CSS — не «стили главной страницы». Это стили конкретного вьюпорта для конкретного шаблона. Один и тот же сайт на мобильном 375px и десктопе 1440px требует разного критического набора.
Отсюда первая развилка: под какой вьюпорт собирать. Инструменты вроде critical (npm-пакет от Addy Osmani) и penthouse извлекают правила, попадающие в заданное окно. Соберёте под десктоп 1440×900 — мобильный пользователь получит FOUC на своём экране, потому что часть его первого экрана не покрыта.
Ошибка 1: media=print как способ асинхронной загрузки
Классический приём для неблокирующей загрузки основной таблицы стилей:
<link rel="stylesheet" href="/styles/main.css"
media="print" onload="this.media='all'">
<noscript>
<link rel="stylesheet" href="/styles/main.css">
</noscript>
Браузер грузит файл с media="print", не блокируя рендер, а после загрузки onload переключает media на all и стили применяются. Работает, но есть две ловушки.
Первая: если JavaScript отключён или упал до onload, основные стили не применятся никогда — отсюда обязательный <noscript>-фолбэк. Многие про него забывают.
Вторая, менее очевидная: между отрисовкой критического CSS и срабатыванием onload есть окно, в котором элементы второго экрана уже видны, но ещё без стилей. При медленной сети пользователь успевает доскроллить и увидеть голый HTML. Это не баг приёма, это его цена — критический CSS обязан покрывать ровно то, что видно сразу.
Ошибка 2: критический CSS собран один раз и забыт
Критический CSS инлайнится в HTML. Если основная таблица стилей поменялась после релиза (переверстали шапку, сменили шрифт), а инлайн-блок остался старым — на первом экране пользователь видит старую вёрстку, которая через доли секунды дёргается к новой. Скачок CLS2 обеспечен.
Лечится только автоматизацией: генерация критического CSS должна быть шагом сборки, а не разовой операцией «сделали руками и вставили». Схематично, для проекта на Node:
// build-critical.mjs — запускается на этапе сборки
// пакет critical ^7.x
import { generate } from 'critical';
await generate({
base: 'dist/',
src: 'index.html',
target: {
html: 'index.html', // инлайнит критический CSS прямо в файл
uncritical: 'styles/uncritical.css',
},
inline: true,
// два вьюпорта — мобильный и десктоп
dimensions: [
{ width: 375, height: 667 },
{ width: 1440, height: 900 },
],
});
Ключевой момент — dimensions с несколькими размерами: пакет объединит правила, попавшие в оба окна. Точный набор опций сверяйте с README пакета вашей версии, API между мажорными версиями critical менялось.
Ошибка 3: инлайн раздулся до сотен килобайт
Смысл критического CSS — сократить время до первой отрисовки. Но если инструмент захватил половину дизайн-системы (например, стили модалок и футера попали в выборку из-за неудачных селекторов), инлайн-блок разрастается. А инлайн-CSS грузится с каждым HTML-ответом и не кешируется браузером отдельно — в отличие от внешнего файла.
Ориентир, который называют в рекомендациях по производительности: критический CSS стоит держать в пределах, при которых он вместе с остальным <head> укладывается в первый пакет ответа сервера. На практике это единицы килобайт, а не десятки. Точную границу под свой стек проверяйте замером TTFB и размера первого чанка.
- Не инлайньте
@font-faceс data-URI шрифтов внутри критического CSS — это самый частый источник раздувания. - Уберите из критического набора анимации и
:hover-состояния — для первой отрисовки они не нужны. - Проверяйте итоговый размер инлайна в сборке и роняйте билд, если он превысил порог.
Ошибка 4: preload без обработки onload
Ещё один способ отложить основной CSS — rel="preload":
<link rel="preload" href="/styles/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="/styles/main.css">
</noscript>
preload сам по себе только качает ресурс, но не применяет его как стиль — для этого нужно переключить rel на stylesheet в onload. Пропустите обработчик — файл скачается и будет висеть в кеше, но стили не применятся. Люди видят «preload» в Lighthouse-советах, копируют без onload и получают сайт совсем без основного CSS.
this.onload=null нужен, чтобы обработчик не сработал повторно после смены rel. В некоторых браузерах переключение rel триггерит onload ещё раз.
Сравнение подходов к отложенной загрузке
| Подход | Что выигрываете | Чем платите |
|---|---|---|
| media=print + onload | Простой, широкая поддержка, есть noscript-фолбэк | Окно FOUC на медленной сети, зависит от JS |
| preload + rel switch | Ранний старт загрузки, приоритет ресурса | Легко забыть onload — CSS не применится вовсе |
| Только критический CSS, остальное по interaction | Минимальный первый ответ | Сложная логика догрузки, риск недостилизованного контента |
| Обычный блокирующий link | Ноль FOUC, ноль JS-зависимости | Блокирует рендер, штраф в Lighthouse |
Когда что выбирать
Критерий простой — считайте, что теряете на блокирующей загрузке. Если основная таблица стилей весит 20–30 КБ и лежит на том же CDN, что и HTML, выигрыш от асинхронной загрузки часто в пределах погрешности, а рисков FOUC и битой вёрстки — море. В этом случае оставьте обычный <link rel="stylesheet"> и не усложняйте.
Отложенная загрузка с критическим CSS оправдана, когда таблица стилей крупная (сотни килобайт), а первый экран простой и хорошо описывается небольшим набором правил — лендинги, статьи, карточки товара. Тогда инлайн критического CSS реально сокращает LCP.
Что ломается чаще всего
- FOUC на втором экране. Критический CSS собран под вьюпорт меньше реального. Соберите под несколько размеров через
dimensions. - Скачок вёрстки при подгрузке основного CSS. Критический набор не задаёт размеры контейнеров и изображений. Пропишите
width/heightилиaspect-ratioдля медиа в критическом наборе. - Стили вообще не применились. Забыт
onloadпри preload илиthis.media='all'при media=print. Проверьте в DevTools, чтоrel/mediaпереключились. - Устаревший инлайн после релиза. Генерация критического CSS не в конвейере сборки. Вынесите в CI-шаг.
Что делать дальше
Проверьте текущее состояние на живом проекте: откройте DevTools → Network, включите throttling «Slow 4G» и посмотрите, есть ли мигание при первой загрузке и скролле. Замерьте LCP и CLS в Lighthouse до и после включения отложенной загрузки — если CLS вырос, критический CSS не задаёт размеры блоков первого экрана. Настройте генерацию критического CSS как шаг сборки с проверкой размера инлайна, чтобы он не разрастался между релизами. В проде смотрите на реальные метрики Core Web Vitals из поля (CrUX или свой RUM), а не только на лабораторный Lighthouse — синтетика не ловит FOUC на медленных устройствах пользователей.
1 Критический CSS (critical CSS) — подмножество CSS-правил, необходимых для отрисовки видимой без прокрутки части страницы.
2 CLS (Cumulative Layout Shift) — метрика Core Web Vitals, оценивающая суммарный сдвиг элементов при загрузке. Скачок вёрстки увеличивает CLS.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: web.dev — Extract and inline critical CSS, critical — npm package repository
Частые вопросы
У меня другая версия пакета critical — приведённый код подойдёт?
Приём с media=print перестал переключать стили — что проверить первым?
Насколько большим может быть инлайн критического CSS?
Нужен ли критический CSS, если сайт на HTTP/2 или HTTP/3?
Grozит ли отложенная загрузка проблемами с SEO?
Как понять, что критический CSS собран под неправильный вьюпорт?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.