На каждом code review, где я вижу <script> в <head> без async/defer, у меня срабатывает conditioned reflex — комментить про render blocking. Но за последний год я трижды осознанно нарушал это правило. Вот когда синхронное исполнение JS до начала рендеринга — это не technical debt, а точный UX-расчёт.
Почему мы default’но избегаем render blocking
Стандартный перформанс-паттерн: браузер при парсинге HTML останавливается на каждом синхронном скрипте, не отрисовывая контент ниже. В 2024 это выглядит архаично, когда у нас есть:
<script async src="..."></script> <!-- Загружает без блокировки, выполняет ASAP -->
<script defer src="..."></script> <!-- Загружает без блокировки, выполняет после DOMContentLoaded -->
Особенно критично для third-party-скриптов (аналитика, реклама, виджеты). Но есть edge cases, где блокировка даёт предсказуемое поведение.
Кейс 1: Instant layout shift prevention
Представьте Single Page Application, где routing происходит на клиенте. Без синхронной проверки auth-статуса в <head> вы можете:
- Отрисовать публичный layout
- Получить ответ от API, что юзер авторизован
- Сделать violent reflow в private UI
// В head, без defer/async
if (checkAuthCookie()) {
document.documentElement.classList.add('authenticated');
// CSS-переменные для авторизованного состояния
document.documentElement.style.setProperty('--nav-bg', '#333');
}
Этот подход мы применили в admin-панели, где flash of public UI выглядел как security issue. Задержка в 200ms до начала рендеринга — acceptable trade-off.
Кейс 2: Critical feature detection
В одном из проектов мы обнаружили, что 5% юзеров получают broken UI из-за отсутствия WebGL support. Проблема в том, что:
- Modern.js-фреймворки рендерят компоненты до feature detection
- Error boundaries ловят ошибки слишком поздно
Решение — синхронный скрипт в <head>:
const isWebGLAvailable = (() => {
try {
return !!window.WebGLRenderingContext && !!document.createElement('canvas').getContext('webgl');
} catch (e) {
return false;
}
})();
if (!isWebGLAvailable) {
window.location.href = '/fallback';
}
Когда это точно anti-pattern
Не используйте render blocking для:
- Аналитики (Google Tag Manager и подобные)
- Ленивой загрузки стилей/шрифтов
- Любого кода, который может быть split на chunks
В нашем случае из трёх инстансов два были связаны с auth flow, один — с hardware capabilities. Во всех случаях UX-выгода перевешивала перформанс-пеналити.
Как измерить impact
Даже осознанная блокировка требует мониторинга. Включите в ваш CI:
// Puppeteer example
const blockingTime = await page.evaluate(() => {
return Math.max(
performance.getEntriesByType('navigation')[0].domContentLoadedEventEnd,
performance.getEntriesByType('paint')[0].startTime
);
});
if (blockingTime > 300) {
throw new Error(`Render blocking exceeded threshold: ${blockingTime}ms`);
}
Если ваш скрипт тормозит рендеринг больше чем на 200-300ms — время оптимизировать или пересмотреть подход. В наших кейсах блокировка укладывалась в 150ms даже на low-end devices.
Render blocking — как !important в CSS. Избегайте по дефолту, но держите в арсенале для особых случаев. Главное — document your decisions, чтобы следующий разработчик не “пофиксил” это рандомным добавлением async в CI.
Источник: https://www.jayfreestone.com/writing/intentional-render-blocking-javascript/