First Contentful Paint (FCP)

Measures the time until the first piece of content is rendered on screen.

Check Your Site's First Contentful Paint

Enter your website URL on the homepage to see your real FCP performance data.

What is FCP?

First Contentful Paint (FCP) measures the time from when the page starts loading to when any part of the page's content is rendered on screen. Content includes text, images (including background images), <svg> elements, and non-white <canvas> elements.

FCP Thresholds

≤ 1.8s

Good

≤ 3.0s

Needs Improvement

> 3.0s

Poor

FCP vs LCP: FCP measures when the first content appears, while LCP measures when the largest content appears. FCP is often much faster than LCP and indicates perceived initial responsiveness.

Quick Wins for FCP

  1. Enable gzip/Brotli compression on your server
  2. Add font-display: swap to all web fonts
  3. Inline critical CSS (above-the-fold styles)
  4. Add defer to non-critical scripts
  5. Use a CDN for static assets

The Critical Rendering Path

Understanding FCP requires understanding the critical rendering path - the sequence of steps the browser takes to convert HTML, CSS, and JavaScript into pixels on screen.

1. HTML

Parse DOM

2. CSS

Build CSSOM

3. JavaScript

Execute

4. Render

Paint pixels

Common Causes of Poor FCP

1. Slow Server Response Time (TTFB)

If the server takes too long to respond with the HTML document, everything else is delayed. High TTFB directly impacts FCP.

2. Render-Blocking Resources

CSS and synchronous JavaScript in the <head>block rendering until they're downloaded and processed.

3. Large CSS Files

The browser must download and parse all CSS before it can render anything. Large CSS files delay FCP significantly.

4. Web Font Blocking

If web fonts block text rendering (FOIT), the browser waits for font download before showing any text content.

How to Improve FCP

1. Reduce Server Response Time

  • Use a CDN to serve content from edge locations
  • Enable server-side caching
  • Optimize database queries
  • Use HTTP/2 or HTTP/3
  • Consider static site generation or edge rendering

2. Eliminate Render-Blocking Resources

  • Inline critical CSS in the <head>
  • Load non-critical CSS asynchronously
  • Use defer or async for JavaScript
  • Remove unused CSS and JavaScript

Example: Load non-critical CSS async

<!-- Critical CSS inline -->
<style>
  /* Above-the-fold styles */
  body { font-family: system-ui; margin: 0; }
  .hero { height: 100vh; display: flex; }
</style>

<!-- Non-critical CSS loaded async -->
<link rel="preload" href="styles.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>

3. Optimize CSS Delivery

  • Minify CSS files
  • Remove unused CSS with tools like PurgeCSS
  • Split CSS per route (CSS code splitting)
  • Use CSS containment

Next.js automatic CSS optimization

// next.config.js
module.exports = {
  experimental: {
    optimizeCss: true, // Enables CSS optimization
  },
}

4. Optimize Font Loading

  • Use font-display: swap to show fallback immediately
  • Preload critical fonts
  • Self-host fonts instead of using Google Fonts
  • Subset fonts to include only needed characters

Example: Optimal font loading

<!-- Preload critical font -->
<link rel="preload" href="/fonts/Inter.woff2"
      as="font" type="font/woff2" crossorigin>

<!-- Font-face with swap -->
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter.woff2') format('woff2');
  font-display: swap;
}

5. Use Preconnect and DNS-Prefetch

  • Preconnect to critical third-party origins
  • DNS-prefetch for less critical origins
  • Preload critical above-the-fold resources

Example: Resource hints

<!-- Preconnect to critical origins -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

<!-- DNS prefetch for less critical -->
<link rel="dns-prefetch" href="https://analytics.example.com">

Measuring FCP

Using the web-vitals library

import { onFCP } from 'web-vitals';

onFCP((metric) => {
  console.log('FCP:', metric.value);

  // Send to analytics
  analytics.track('FCP', {
    value: metric.value,
    rating: metric.rating,
    navigationType: metric.navigationType,
  });
});