Урок 05

Критический CSS

Проблема

Что происходит:

  • CSS блокирует рендер.
  • Большой CSS — секунды.
  • Пользователь видит белый экран.
  • LCP страдает.

Правило: CSS в <head> блокирует первую отрисовку.

Что такое критический CSS

Critical CSS — стили первого экрана.

Что входит:

  • Header.
  • Hero.
  • Nav.
  • Основная типографика.
  • Кнопки в первом экране.

Что не входит:

  • Footer.
  • Модалки.
  • Ниже первого экрана.

Правило: критический = что видно сразу.

Идея

Что делаем:

  1. Вырезаем критический CSS.
  2. Инлайним в <head>.
  3. Остальное — асинхронно.
  4. После загрузки — подключаем полный.

Что даёт:

  • Мгновенный рендер.
  • 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.