22 / 36
Что такое First Contentful Paint (FCP)?
Полный ответ
FCP -- часть цепочки метрик загрузки:
JavaScript
TTFB FCP LCP
| | |
v v v
[сервер] [первый текст] [основной контент]
Пользователь видит:
Белый экран | Что-то появилось | Страница загружена
Измерение FCP:
JavaScript
// Через PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log(`FCP: ${entry.startTime.toFixed(2)} ms`);
observer.disconnect();
}
}
});
observer.observe({ type: 'paint', buffered: true });
JavaScript
// Через web-vitals библиотеку (рекомендуемый способ)
import { onFCP } from 'web-vitals';
onFCP((metric) => {
console.log(`FCP: ${metric.value} ms`);
console.log(`Рейтинг: ${metric.rating}`); // "good", "needs-improvement", "poor"
});
Что считается "контентом" для FCP:
HTML
<!-- ✅ Засчитывается как FCP -->
<p>Любой текст</p>
<img src="photo.jpg" alt="Фото">
<svg>...</svg>
<canvas>
<!-- если на нём что-то нарисовано -->
</canvas>
<video poster="thumbnail.jpg">
<!-- poster-изображение -->
</video>
<!-- ❌ НЕ засчитывается -->
<div style="background: white">
</div>
<!-- пустой фон -->
<canvas>
</canvas>
<!-- пустой canvas -->
Основные причины медленного FCP и их устранение:
HTML
<!-- ❌ Блокирующий CSS задерживает FCP -->
<head>
<link rel="stylesheet" href="huge-bundle.css">
<!-- 200 КБ CSS -->
</head>
<!-- ✅ Инлайн критический CSS, остальное -- async -->
<head>
<style>
/* Только стили для первого экрана */
body { font-family: system-ui; margin: 0; }
.header { background: #1a1a2e; color: white; padding: 16px; }
.hero { padding: 48px 24px; }
</style>
<link rel="preload" href="full.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet" href="full.css">
</noscript>
</head>
HTML
<!-- ❌ Блокирующий JS в <head> -->
<head>
<script src="analytics.js">
</script>
<!-- парсинг HTML остановлен -->
<script src="app.js">
</script>
<!-- и тут тоже -->
</head>
<!-- ✅ defer / async -->
<head>
<script src="analytics.js" async>
</script>
<!-- не блокирует парсинг -->
<script src="app.js" defer>
</script>
<!-- выполнится после парсинга HTML -->
</head>
JavaScript
// ❌ SPA без SSR -- пустая страница, пока JS не загрузится и не выполнится
// index.html содержит только <div id="root"></div>
// FCP наступит только после загрузки и выполнения React-бандла
// ✅ SSR / SSG -- HTML уже содержит контент
// Сервер отдаёт готовый HTML:
// <div id="root"><header>...</header><main>...</main></div>
// FCP наступает сразу после парсинга HTML, без ожидания JS
📌 Реальные кейсы
Шрифты блокируют FCP:
CSS
/* ❌ Браузер ждёт загрузки шрифта, текст невидим */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
/* font-display по умолчанию: auto (обычно block) */
}
/* ✅ font-display: swap -- показываем системный шрифт, потом меняем */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
}
HTML
<!-- Предзагрузка критического шрифта -->
<link rel="preload" href="/fonts/custom.woff2" as="font"
type="font/woff2" crossorigin>
Резюме
FCP показывает, как быстро пользователь видит первый контент. Основные враги FCP: блокирующие CSS и JS в <head>, тяжёлые шрифты без font-display: swap, отсутствие SSR в SPA. Целевое значение -- до 1.8 секунды. Улучшение TTFB автоматически улучшает FCP.