Render-blocking ресурсы: как ускорить first paint без жертв
Что такое render-blocking CSS и JS, почему они портят LCP, и какие практические техники их устраняют — preload, async/defer, critical CSS, code splitting.
«Сайт долго грузится» — самая частая жалоба пользователей и самая частая причина потери позиций в SEO. И в 80% случаев виновник один: render-blocking ресурсы. Это CSS и JS файлы, которые браузер должен скачать и обработать перед тем, как нарисовать пользователю хоть что-то на экране.
В этой статье — что такое render-blocking, как их найти и какими техниками устранить без потери функциональности сайта.
Как работает рендеринг страницы
Чтобы понять проблему, нужно понимать как браузер строит страницу.
- Браузер запрашивает HTML
- Парсит HTML, строит DOM-дерево
- Встречает
<link rel="stylesheet">или<script src="">в<head> - Останавливает построение и идёт скачивать ресурс
- Скачивает, обрабатывает (парсит CSS, выполняет JS)
- Возвращается к парсингу HTML
- Финально строит CSSOM, объединяет с DOM в Render Tree
- Делает Layout (geometry), Paint, Compositing
- Пользователь видит первый кадр
Если на шаге 3-5 ресурс скачивается медленно, пользователь смотрит на белый экран. LCP (Largest Contentful Paint) пухнет. Bounce rate растёт.
Render-blocking ресурс — любой CSS или JS, который браузер обязан скачать и обработать до начала рендеринга.
Как найти render-blocking ресурсы
Откройте PageSpeed Insights на главной вашего сайта. В разделе «Opportunities» обычно есть строка «Eliminate render-blocking resources» с списком файлов и сколько времени можно сэкономить.
Также:
- Chrome DevTools → Network, перезагрузите страницу. Файлы с типом script и stylesheet, загруженные ДО события
DOMContentLoaded— render-blocking. - Chrome DevTools → Coverage (открыть через Cmd+Shift+P → «Show Coverage»). Покажет какой % каждого ресурса фактически использовался на текущей странице. Если у вас 200 КБ CSS, а используется 15 КБ — есть что оптимизировать.
- Lighthouse → Audits → Performance — то же что PageSpeed но локально.
Техника 1: async и defer для JS
Это самая базовая оптимизация, которую все знают, но не все применяют правильно.
<script src="analytics.js"></script> <!-- блокирует -->
<script src="analytics.js" async></script> <!-- скачивается параллельно, выполняется как готов -->
<script src="analytics.js" defer></script> <!-- скачивается параллельно, выполняется после DOMContentLoaded -->
Когда async:
- Аналитика (Я.Метрика, GA, Hotjar)
- Виджеты соцсетей
- Любой JS, который не зависит от DOM и от других скриптов
Когда defer:
- JS, который работает с DOM (находит элементы, навешивает обработчики)
- JS, который должен выполниться в определённом порядке (несколько defer-скриптов выполняются последовательно в порядке появления в HTML)
- Полифиллы и тяжёлые библиотеки
Когда оставить блокирующим:
- Очень редко. Только если скрипт критически нужен для отрисовки above-the-fold контента и нельзя обойтись CSS.
Техника 2: критический CSS inline в HTML
CSS блокирует рендеринг иначе, чем JS — браузер должен дождаться полного CSSOM перед первым paint. Поэтому даже маленький блокирующий CSS — это +300-500 мс к LCP на медленном соединении.
Решение: критический CSS — стили, нужные только для above-the-fold блока, инлайнятся прямо в <head> через <style>. Остальные CSS подгружаются асинхронно.
<head>
<style>
/* Critical CSS: header, hero, главные кнопки. Обычно 8-15 КБ. */
body { font: 16px/1.5 sans-serif; margin: 0; }
.header { padding: 20px; background: #fff; }
.hero { padding: 60px 20px; text-align: center; }
/* ... */
</style>
<!-- Остальной CSS — асинхронно -->
<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>
</head>
Сложность: критический CSS меняется при изменении дизайна. Поддержка вручную невозможна. Используйте автоматические инструменты:
- Critters (npm package, интегрируется в build)
- PurgeCSS + Critical для статических сайтов
- Next.js делает это автоматически через CSS Modules + RSC
- Astro инлайнит критический CSS из default scope автоматически
Техника 3: code splitting в JS
Если у вас одно большое bundle.js весом 800 КБ, оно блокирует/тяжелит загрузку каждой страницы — даже там, где нужны только 50 КБ функционала.
Решение: разбивайте код на чанки по маршрутам и фичам.
В Next.js это работает из коробки — каждая страница получает свой chunk + общий shared chunk. Дополнительно для тяжёлых компонентов:
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
ssr: false,
loading: () => <Skeleton />
});
HeavyChart.js не попадёт в основной бандл — будет загружен отдельным запросом только когда страница его покажет.
В React без Next.js — React.lazy() + Suspense:
const HeavyChart = React.lazy(() => import('./HeavyChart'));
<Suspense fallback={<Skeleton />}>
<HeavyChart />
</Suspense>
В Vue 3 — defineAsyncComponent.
Техника 4: preload ключевых ресурсов
Если ресурс точно понадобится для рендера, можно сказать браузеру «начни скачивать его сразу, не жди парсинга HTML».
<head>
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/hero-image.jpg" as="image">
</head>
Это особенно важно для:
- Шрифтов (без preload браузер находит
@font-faceв CSS только после загрузки CSS — FOUT/FOIT) - Главного hero-изображения (LCP кандидат)
- Критичных JS-чанков (если используете code splitting)
Не злоупотребляйте preload — каждый preload отъедает приоритет у других ресурсов. 3-5 preload на страницу — норма, 30 — деградация.
Техника 5: третьи стороны под контроль
Третьи стороны — самый частый источник render-blocking в 2026 году. Аналитика, рекламные сети, чаты, виджеты соцсетей. Каждый виджет = +1 синхронный JS на 50-200 КБ.
Стратегии:
- Аудит: посмотрите все третьи стороны на сайте. Каждую — оцените: «нужна ли она реально». Часто 60% можно удалить без потерь.
- Async для всех: добавьте
asyncилиdeferко всем третьесторонним скриптам. - Lazy loading: некоторые виджеты можно загружать только когда нужны. Например, чат интеркома — после первого взаимодействия пользователя, не сразу.
- Self-hosting аналитики: вместо
<script src="https://google-analytics.com/...">— проксируйте через свой домен. Будет быстрее (один TCP-handshake), стабильнее (не зависит от блокировщиков), приватнее. - Tag manager только если реально нужен: Google Tag Manager — это +200 КБ на каждую страницу. Если у вас 3 скрипта, проще включить их напрямую.
Техника 6: HTTP/2 и HTTP/3
Это инфраструктурный шаг, но он влияет на render-blocking.
В HTTP/1.1 браузер ограничен 6 параллельными запросами к одному домену. Если у вас в head 10 ресурсов — 4 ждут очереди. Render-blocking накапливается.
В HTTP/2 — мультиплексирование, один TCP-канал передаёт все ресурсы одновременно. В HTTP/3 (QUIC) — дополнительно быстрое восстановление при потере пакетов.
Что нужно сделать:
- Включить HTTP/2 в nginx (он по умолчанию есть с 1.13+, проверьте
listen 443 ssl http2;) - HTTP/3 пока более экспериментально, но nginx 1.25+ его поддерживает:
listen 443 ssl http2 http3; - CDN — большинство современных CDN (Cloudflare, BunnyCDN, KeyCDN) поддерживают HTTP/3 из коробки
Эффект: на сайтах с 20+ ресурсами в head ускорение LCP на 200-500 мс просто от включения HTTP/2.
Реальный пример
Типичный сайт «до»:
- 1 главный CSS — 145 КБ, render-blocking
- 1 jQuery — 88 КБ, render-blocking
- 1 main.js — 320 КБ, render-blocking
- 4 third-party (analytics, chat, Hotjar, ads) — 280 КБ суммарно
- LCP: 4.2 сек (мобильный, 4G)
«После» оптимизации:
- Critical CSS inline 12 КБ + main.css preload 145 КБ
- jQuery вообще удалили (не используется в коде)
- main.js — defer, разбит на 6 чанков
- Все third-party — async или удалены
- LCP: 1.8 сек
Эффект: bounce rate упал на 32%, конверсия выросла на 18%, позиции в Яндексе по 60% запросов выросли в среднем на 4.
Резюме
Render-blocking ресурсы — главная причина медленного first paint. Устраняются они комбинацией техник: async/defer для JS, critical CSS inline, code splitting, preload, контроль третьих сторон, HTTP/2.
Большинство — это однократные оптимизации, которые потом стабильно работают. Час работы инженера может сэкономить пользователям часы суммарного ожидания.
Хотите аудит производительности? Напишите нам — бесплатный экспресс-аудит за 2 рабочих дня.