Detailed guide

Fcp Optimization Guide

View contents

Complete FCP Optimization Guide: Achieving Fast First Contentful Paint

Introduction

First Contentful Paint (FCP) measures when users first see content on your page. Achieving a good FCP score (under 1.8 seconds) requires optimizing the critical rendering path—the sequence of steps browsers take to convert HTML, CSS, and JavaScript into visible pixels.

This guide covers advanced strategies to eliminate delays and get content to users as quickly as possible.

Understanding the Critical Rendering Path

Before FCP can occur, the browser must:

Critical Rendering Path:
1. Download HTML
2. Parse HTML, discover resources
3. Download critical CSS
4. Parse CSS, build CSSOM
5. Build render tree (DOM + CSSOM)
6. Calculate layout
7. Paint content → FCP!

Key insight: FCP is blocked by:

  • HTML download and parsing
  • CSS download and parsing
  • Synchronous JavaScript

Optimizing Server Response Time

1. Improve TTFB (Time to First Byte)

Every millisecond of TTFB delays FCP:

TTFB Components:
├── DNS Lookup: 20-120ms
├── TCP Connection: 20-100ms
├── TLS Handshake: 30-100ms
├── Server Processing: Variable
└── Network Latency: Variable

Optimization targets:
├── Total TTFB < 200ms (excellent)
├── Total TTFB < 500ms (good)
└── Total TTFB > 800ms (needs work)

2. Use a CDN

Serve content from edge servers near users:

Without CDN:
User (Chile) → Origin Server (US East) = 180ms latency

With CDN:
User (Chile) → CDN Edge (Santiago) = 20ms latency

3. Implement Caching

# Nginx - Cache static resources
location ~* \.(css|js|jpg|jpeg|png|webp|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# Cache HTML with revalidation
location / {
    add_header Cache-Control "public, max-age=0, must-revalidate";
}

4. Enable Compression

# Enable Brotli (preferred) and Gzip compression
gzip on;
gzip_types text/plain text/css application/javascript application/json;
gzip_min_length 256;

# Brotli (if available)
brotli on;
brotli_types text/plain text/css application/javascript application/json;

Eliminating Render-Blocking CSS

1. Identify Critical CSS

Critical CSS = styles needed for above-the-fold content:

<!-- Critical CSS inlined in <head> -->
<style>
/* Only styles for initial viewport */
:root { --primary: #071952; --text: #333; }
body { margin: 0; font-family: system-ui; color: var(--text); }
.header { background: var(--primary); padding: 1rem; }
.hero { padding: 2rem; }
.hero h1 { font-size: 2.5rem; margin: 0; }
</style>

2. Extract and Inline Critical CSS

Use tools to automatically extract critical CSS:

# Using critical npm package
npx critical index.html --inline --minify

# Using PurgeCSS for unused CSS removal
npx purgecss --css styles.css --content index.html

3. Load Non-Critical CSS Asynchronously

<!-- Pattern 1: preload + onload -->
<link rel="preload" href="full-styles.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">
<noscript>
    <link rel="stylesheet" href="full-styles.css">
</noscript>

<!-- Pattern 2: media query swap -->
<link rel="stylesheet" href="full-styles.css"
      media="print" onload="this.media='all'">

<!-- Pattern 3: Dynamic injection -->
<script>
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = 'full-styles.css';
    document.head.appendChild(link);
</script>

4. Minimize CSS

Keep CSS small and efficient:

/* ❌ Verbose CSS */
.button {
    background-color: #071952;
    padding-top: 10px;
    padding-bottom: 10px;
    padding-left: 20px;
    padding-right: 20px;
    border-radius: 4px;
}

/* ✅ Minified CSS */
.button{background:#071952;padding:10px 20px;border-radius:4px}

Eliminating Render-Blocking JavaScript

1. Defer Non-Critical Scripts

<!-- ❌ Blocks rendering -->
<script src="analytics.js"></script>

<!-- ✅ Deferred - runs after DOM ready -->
<script src="analytics.js" defer></script>

<!-- ✅ Async - runs when downloaded, order not guaranteed -->
<script src="widget.js" async></script>

2. Move Scripts to End of Body

<!DOCTYPE html>
<html>
<head>
    <style>/* Critical CSS only */</style>
</head>
<body>
    <!-- Content that triggers FCP -->
    <header>...</header>
    <main>...</main>

    <!-- Scripts at end -->
    <script src="app.js"></script>
</body>
</html>

3. Use Module Scripts

<!-- Module scripts are deferred by default -->
<script type="module" src="app.js"></script>

<!-- Preload critical modules -->
<link rel="modulepreload" href="critical-module.js">

Optimizing Resource Loading

1. Preload Critical Resources

Tell browser what’s important:

<head>
    <!-- Preload critical CSS if not inlined -->
    <link rel="preload" href="critical.css" as="style">

    <!-- Preload hero image -->
    <link rel="preload" href="hero.webp" as="image"
          fetchpriority="high">

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

2. Use Resource Hints

<!-- DNS prefetch for third-party domains -->
<link rel="dns-prefetch" href="https://cdn.example.com">

<!-- Preconnect for critical third parties -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- Prefetch resources for next navigation -->
<link rel="prefetch" href="next-page.html">

3. Prioritize Visible Content

Use fetchpriority attribute:

<!-- High priority for LCP image -->
<img src="hero.webp" fetchpriority="high" alt="Hero">

<!-- Low priority for below-fold images -->
<img src="footer-logo.webp" fetchpriority="low" alt="Logo">

Font Optimization for FCP

1. Use font-display

@font-face {
    font-family: 'CustomFont';
    src: url('font.woff2') format('woff2');
    font-display: swap; /* Show fallback immediately, swap when ready */
}

2. Preload Critical Fonts

<link rel="preload" href="primary-font.woff2"
      as="font" type="font/woff2" crossorigin>

3. Use System Font Stack as Fallback

body {
    font-family: 'CustomFont', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

4. Subset Fonts

Remove unused characters:

# Using glyphanger
glyphanger --subset="*.woff2" --whitelist="U+0-7F"

Server-Side Rendering Benefits

SSR can dramatically improve FCP:

Client-Side Rendering (CSR):
HTML Shell → JS Download → JS Execute → Content Render → FCP
            └────────────── Long delay ──────────────┘

Server-Side Rendering (SSR):
Full HTML → FCP! (immediate) → JS Download → Hydration
↑ FCP happens much faster

Static Site Generation (SSG)

For content that doesn’t change often:

SSG Process:
Build Time: Generate HTML for all pages
Runtime: Serve pre-built HTML → Immediate FCP

Measuring and Monitoring FCP

Using Performance API

// Get FCP value
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        if (entry.name === 'first-contentful-paint') {
            console.log('FCP:', entry.startTime, 'ms');
        }
    }
});
observer.observe({ type: 'paint', buffered: true });

Using 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, // 'good', 'needs-improvement', 'poor'
        url: window.location.href
    });
});

FCP Optimization Checklist

Server Response
├── [ ] TTFB under 200ms
├── [ ] CDN configured
├── [ ] Compression enabled (Brotli/Gzip)
├── [ ] HTTP/2 or HTTP/3 enabled
└── [ ] Proper caching headers

CSS Optimization
├── [ ] Critical CSS inlined
├── [ ] Non-critical CSS loaded async
├── [ ] CSS minified
├── [ ] Unused CSS removed
└── [ ] No @import statements

JavaScript Optimization
├── [ ] Scripts deferred or async
├── [ ] No render-blocking JS in head
├── [ ] Critical JS inlined if small
└── [ ] Third-party scripts deferred

Resource Loading
├── [ ] Critical resources preloaded
├── [ ] Resource hints configured
├── [ ] Images optimized
└── [ ] Fonts preloaded with font-display

Monitoring
├── [ ] FCP tracked in RUM
├── [ ] Alerts for FCP regression
├── [ ] Regular Lighthouse audits
└── [ ] Field data reviewed in CrUX

Common FCP Problems and Solutions

Problem Solution
Large CSS file blocks render Extract and inline critical CSS
Synchronous JS in head Add defer or move to body end
Slow server response Use CDN, enable caching
Large HTML document Reduce DOM size, use SSR
Slow third-party resources Defer, use facades, or remove
Web fonts delay text Use font-display: swap, preload

References

This article was enhanced using authoritative sources:

[1] web.dev: Optimize First Contentful Paint https://web.dev/articles/optimize-fcp Google’s official guide to FCP optimization. Covers the critical rendering path, render-blocking resources, and specific techniques for improving FCP including CSS delivery and JavaScript loading strategies.

[2] Chrome Developers: Critical Rendering Path https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains Documentation on critical request chains and how to optimize resource loading order. Explains how request chains affect FCP and how to minimize chain depth.

[3] MDN Web Docs: CSS Performance Optimization https://developer.mozilla.org/en-US/docs/Learn/Performance/CSS Technical reference for CSS optimization including critical CSS extraction, selector efficiency, and async loading patterns that directly impact FCP.

[4] web.dev: Eliminate Render-Blocking Resources https://web.dev/articles/render-blocking-resources Comprehensive guide to identifying and removing render-blocking CSS and JavaScript. Covers inline critical CSS, defer/async attributes, and resource prioritization.


Note: This article is part of our SEO analysis series. Explore all articles in the Performance SEO Hub.


Sources: web.dev (FCP Optimization), Chrome Developers, MDN Web Docs

Related articles

Related version

Introduction

Fcp Explained

First Contentful Paint (FCP) measures the time from when a page starts loading to when any part of the page's content is rendered on the screen

Category hub

Hub

Performance Seo Hub

Performance is a critical ranking factor and directly impacts user experience

In the same category

Detailed guide

Lcp Optimization Guide

Largest Contentful Paint (LCP) measures when the largest content element becomes visible to users

Detailed guide

Ttfb Optimization Guide

Time to First Byte (TTFB) is the foundation of web performance—every millisecond of TTFB delays your entire page load