Критический CSS
Проблема
Что происходит:
- CSS блокирует рендер.
- Большой CSS — секунды.
- Пользователь видит белый экран.
- LCP страдает.
Правило: CSS в <head> блокирует первую отрисовку.
Что такое критический CSS
Critical CSS — стили первого экрана.
Что входит:
- Header.
- Hero.
- Nav.
- Основная типографика.
- Кнопки в первом экране.
Что не входит:
- Footer.
- Модалки.
- Ниже первого экрана.
Правило: критический = что видно сразу.
Идея
Что делаем:
- Вырезаем критический CSS.
- Инлайним в
<head>. - Остальное — асинхронно.
- После загрузки — подключаем полный.
Что даёт:
- Мгновенный рендер.
- LCP ↓ в 2–3×.
- FCP раньше.
Инлайн
<head>
<style>
/* Критический CSS */
body { margin: 0; font-family: system-ui; }
.header { display: flex; padding: 16px; background: #fff; }
.hero { padding: 80px 0; text-align: center; }
.btn { padding: 12px 24px; background: #c1442e; color: #fff; }
</style>
<link
rel="preload"
href="/css/main.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
>
<noscript>
<link rel="stylesheet" href="/css/main.css">
</noscript>
</head>
Что важно:
- Inline — стили выше экрана.
- Preload +
onload— асинхронная загрузка. <noscript>— без JS.
Правило: <noscript> — обязателен.
Извлечение
npm пакет critical:
npm install -D critical
Node-скрипт:
import { generate } from 'critical';
await generate({
inline: true,
base: 'dist/',
src: 'index.html',
target: 'index.html',
width: 1300,
height: 900,
penthouse: {
blockJSRequests: false,
},
});
Что даёт: автоматически вырезает.
Правило: в сборке — автоматически.
Разные страницы
Проблема: каждая страница — свой критический CSS.
Что делать:
- Per-page critical.
- Или общий critical для top-3 страниц.
- Остальные — с общим.
Правило: главная + категории — приоритет.
Устаревание
Проблема: дизайн меняется.
Что делать:
- Пересобирать critical в CI.
- Не хранить вручную.
- Автоматизация — обязательна.
Правило: critical CSS — только из сборки.
Preload
<link rel="preload" href="/css/main.css" as="style">
Что даёт: начало загрузки до парсинга body.
Правило: preload — всегда для полного CSS.
Async load
Вариант 1:
<link
rel="preload"
as="style"
href="main.css"
onload="this.onload=null;this.rel='stylesheet'"
>
Вариант 2:
<link rel="stylesheet" href="main.css" media="print" onload="this.media='all'">
Правило: onload + rel — стандарт.
Media queries
<link rel="stylesheet" href="desktop.css" media="(min-width: 1024px)">
<link rel="stylesheet" href="mobile.css" media="(max-width: 1023px)">
Что даёт: загружается только нужный.
Правило: для больших — разделять.
Remove unused CSS
PurgeCSS:
npm install -D @fullhuman/postcss-purgecss
export default {
plugins: [
require('@fullhuman/postcss-purgecss')({
content: ['./src/**/*.{html,js,jsx,tsx}'],
safelist: [/^modal/, /^tooltip/],
}),
],
};
Что даёт: минус 50–90% CSS.
Правило: PurgeCSS + critical — вместе.
Почему Purge не для critical
Что важно:
- Purge удаляет неиспользуемый CSS.
- Critical вырезает выше экрана.
- Purge сначала, потом critical.
Правило: порядок важен.
Inline vs External
Inline критический:
- Плюсы: нет запроса, рендер сразу.
- Минусы: нельзя кэшировать, размер HTML.
External полный:
- Плюсы: кэш.
- Минусы: блокирует.
Правило: критический inline ≤ 14 KB.
Ограничение 14 KB
Почему: первая TCP-пачка ~14 KB.
Что даёт: инлайн критический укладывается в 1 раунд.
Правило: critical — ужать до 14 KB.
HTTP/2 / HTTP/3
Что важно:
- HTTP/2 — мультиплексирование.
- HTTP/3 — быстрее.
- Много мелких запросов — не проблема.
Правило: HTTP/3 — для критичного.
Пакеты
Что использовать:
- critical — npm.
- penthouse — под капотом.
- @loadable/server — для SSR.
- critters — Webpack-плагин.
Правило: в Webpack/Vite — плагин, не CLI.
Vite плагин
npm install -D vite-plugin-critical
import critical from 'vite-plugin-critical';
export default {
plugins: [critical({ criticalUrl: 'http://localhost:3000' })],
};
Правило: в Vite — плагин.
Next.js
Из коробки:
- CSS инлайнится для CSS Modules.
styled-jsx— инлайн.next/font— preload шрифтов.
Что добавить:
critters— если Tailwind.
Правило: в Next.js — автоматически.
Что НЕ делать
- Critical вручную.
- External CSS без preload.
- Без noscript.
- Огромный inline (> 50 KB).
- External только.
- Забыть про font-face.
Практика
1. Установите critical.
2. Прогоните на главной.
3. Проверьте inline CSS.
4. Добавьте preload + onload.
5. <noscript> — есть?
6. PurgeCSS — включён?
7. Замерьте FCP.
8. Замерьте LCP.
9. Настройте для 3 страниц.
10. Включите в CI.
Частые ошибки
1. Critical вручную:
Устаревает
Решение: автоматически.
2. Без preload:
Поздняя загрузка
Решение: preload.
3. Без noscript:
Без JS — без стилей
Решение: добавить.
4. > 14 KB inline:
Два раунда
Решение: ужать.
5. Без Purge:
Много лишнего
Решение: PurgeCSS.
6. Purge после critical:
Может сломать
Решение: Purge сначала.
7. Забыть onload:
Блокирует
Решение: добавить.
8. Без CI:
Устаревшее
Решение: в сборку.
Итог
- Critical CSS — стили первого экрана.
- Inline в
<head>. preload+onload— async.<noscript>— обязательно.- Авто — в сборке.
- ≤ 14 KB — предел.
- Purge сначала — потом critical.
- Media queries — для крупных.
- HTTP/3 — для скорости.
- CI — регулярная пересборка.
Дальше — HTTP/2 и HTTP/3.