Перейти к содержимому
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.

Что такое First Contentful Paint (FCP)? | JScriptiser