Technical

First Contentful Paint (FCP)

A performance metric that measures the time from when a page starts loading to when any part of the page's content is rendered on the screen.

Quick Answer

  • What it is: A performance metric that measures the time from when a page starts loading to when any part of the page's content is rendered on the screen.
  • Why it matters: FCP marks the first moment users see that your page is actually loading, reducing perceived wait time and abandonment.
  • How to check or improve: Optimize critical rendering path, minimize render-blocking resources, and prioritize above-the-fold content for FCP under 1.8s.

When you'd use this

FCP marks the first moment users see that your page is actually loading, reducing perceived wait time and abandonment.

Example scenario

Hypothetical scenario (not a real company)

A team might use First Contentful Paint (FCP) when Optimize critical rendering path, minimize render-blocking resources, and prioritize above-the-fold content for FCP under 1.8s.

Common mistakes

  • Confusing First Contentful Paint (FCP) with Largest Contentful Paint (LCP): A Core Web Vitals metric that measures the render time of the largest image or text block visible within the viewport, relative to when the page first started loading.
  • Confusing First Contentful Paint (FCP) with Time to First Byte (TTFB): A web performance metric that measures the time from when a browser requests a page until it receives the first byte of data from the web server.
  • Confusing First Contentful Paint (FCP) with Core Web Vitals: A set of three specific metrics that Google uses to measure user experience on websites: loading performance (LCP), interactivity (INP), and visual stability (CLS). Learn how to measure and improve your Core Web Vitals scores.

How to measure or implement

  • Optimize critical rendering path, minimize render-blocking resources, and prioritize above-the-fold content for FCP under 1
  • 8s

Analyze your FCP performance

Start here
Updated Jan 20, 2026·5 min read

First Contentful Paint (FCP) is the performance metric that captures the moment users first see visual confirmation that your page is loading. Unlike TTFB which measures server response, or LCP which waits for the largest element, FCP marks when any content first appears—whether it's text, an image, or even a loading spinner. This makes it a critical metric for perceived performance.

What is First Contentful Paint?

FCP measures the time from navigation start (when the user clicks a link or enters a URL) to when the browser renders the first bit of content from the DOM. This content can be:

  • Text (with any font, including web fonts)
  • Images (including background images)
  • SVG elements
  • Non-white canvas elements
  • Video elements (poster frame)

FCP specifically excludes:

  • iframes (content inside iframes doesn't count)
  • White or transparent backgrounds
  • Content hidden with CSS

Google's FCP thresholds are:

  • Good: 1.8 seconds or less
  • Needs Improvement: 1.8 to 3.0 seconds
  • Poor: Over 3.0 seconds

These thresholds apply to the 75th percentile of page loads, segmented across mobile and desktop devices.

Why FCP Matters for User Experience

The Psychology of First Paint

FCP addresses a fundamental aspect of human psychology: the need for feedback. When users click a link, they need immediate confirmation that something is happening. Without this feedback:

  • Users assume the site is broken
  • They click multiple times (rage clicking)
  • They hit the back button
  • They develop negative brand associations

Research shows:

  • 53% of users abandon sites that take over 3 seconds to show content
  • 1-second delay in FCP reduces conversions by 7%
  • 100ms improvement in FCP increases engagement by 1.5%

FCP's Role in the Performance Perception

FCP is part of the progressive rendering experience:

  1. FCP (First Contentful Paint): Something appears
  2. FMP (First Meaningful Paint): Primary content visible
  3. LCP (Largest Contentful Paint): Main content rendered
  4. TTI (Time to Interactive): Page fully interactive

Users perceive sites with fast FCP as significantly faster overall, even if total load time is similar. This "perceived performance" often matters more than actual load time.

Business Impact

Companies optimizing FCP report significant improvements:

Pinterest reduced FCP by 40%:

  • 15% increase in SEO traffic
  • 15% increase in signup conversion

Walmart improved FCP by 1 second:

  • 2% increase in conversions
  • 1% increase in revenue per visitor

BBC found that each additional second to FCP:

  • Lost 10% of users
  • Reduced page views by 4.5%

How FCP Works: The Rendering Pipeline

The Critical Rendering Path

FCP depends on the critical rendering path—the sequence of steps browsers must complete before rendering:

Navigation Start
    ↓
DNS + TCP + TLS
    ↓
HTTP Request/Response (TTFB)
    ↓
Parse HTML
    ↓
Parse CSS → Build CSSOM
    ↓
Parse JavaScript (if render-blocking)
    ↓
Build Render Tree
    ↓
Layout/Reflow
    ↓
Paint ← FCP MEASURED HERE

Render-Blocking Resources

Resources that delay FCP:

CSS:

  • All CSS is render-blocking by default
  • Browser waits for CSSOM construction
  • Even non-critical CSS blocks first paint

JavaScript:

  • Synchronous scripts block parsing
  • Scripts without async or defer delay rendering
  • Inline scripts can block if placed incorrectly

Fonts:

  • Can cause Flash of Invisible Text (FOIT)
  • Delays text rendering until fonts load
  • Without font-display, text remains invisible

Browser Optimizations

Modern browsers implement optimizations affecting FCP:

Speculative Parsing: Browsers pre-scan for resources while blocked:

<!-- Browser discovers and fetches image while parsing CSS -->
<link rel="stylesheet" href="styles.css" />
<img src="hero.jpg" />

Early Flush: Servers can send partial HTML:

// Node.js early flush example
app.get('/', (req, res) => {
  res.write(`
    <!DOCTYPE html>
    <html>
    <head>
      <link rel="stylesheet" href="/critical.css">
    </head>
    <body>
      <header>Loading...</header>
  `);

  // Flush early content
  res.flush();

  // Process and send rest
  const content = await generateContent();
  res.end(content);
});

Best Practices for Optimizing FCP

1. Optimize the Critical Rendering Path

Inline Critical CSS

<style>
  /* Critical above-the-fold styles */
  body {
    margin: 0;
    font-family: system-ui;
  }
  .header {
    background: #333;
    color: white;
    padding: 1rem;
  }
  .hero {
    min-height: 400px;
    background: #f0f0f0;
  }
</style>

<!-- Load non-critical CSS asynchronously -->
<link
  rel="preload"
  href="/css/main.css"
  as="style"
  onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="/css/main.css" /></noscript>

Extract Critical CSS Automatically

// Using critical npm package
const critical = require("critical")

critical.generate({
  inline: true,
  base: "dist/",
  src: "index.html",
  target: "index-critical.html",
  width: 1300,
  height: 900,
  extract: true, // Extract and inline critical CSS
  ignore: {
    atrule: ["@font-face"]
  }
})

2. Eliminate Render-Blocking JavaScript

Use Async and Defer

<!-- Bad: Render-blocking -->
<script src="app.js"></script>

<!-- Good: Deferred execution -->
<script defer src="app.js"></script>

<!-- Good: Async for independent scripts -->
<script async src="analytics.js"></script>

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

Load JavaScript Conditionally

<script>
  // Load non-critical features after FCP
  if ("requestIdleCallback" in window) {
    requestIdleCallback(() => {
      import("./features.js")
    })
  } else {
    setTimeout(() => import("./features.js"), 1)
  }
</script>

3. Optimize Font Loading

Use font-display

@font-face {
  font-family: "Brand Font";
  src: url("/fonts/brand.woff2") format("woff2");
  font-display: swap; /* Show fallback immediately */

  /* Other options:
   * block - Hide text up to 3s (FOIT)
   * swap - Show fallback immediately (FOUT)
   * fallback - Hide briefly, then fallback
   * optional - Use if cached, else fallback
   */
}

Preload Key Fonts

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

<!-- Preconnect to font providers -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

4. Implement Server-Side Rendering (SSR)

Next.js SSR Example

// pages/index.js
export async function getServerSideProps() {
  const data = await fetchData()

  return {
    props: { data } // Pre-rendered with data
  }
}

export default function Home({ data }) {
  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
    </div>
  )
}

Static Generation for Better FCP

// Even better: Static generation
export async function getStaticProps() {
  const data = await fetchData()

  return {
    props: { data },
    revalidate: 3600 // Regenerate every hour
  }
}

5. Optimize Network Performance

Enable Compression

# Nginx gzip configuration
gzip on;
gzip_types text/plain text/css text/javascript
           application/javascript application/json
           text/xml application/xml image/svg+xml;
gzip_min_length 1000;
gzip_comp_level 6;

# Brotli compression (better than gzip)
brotli on;
brotli_types text/plain text/css text/javascript
             application/javascript application/json;
brotli_comp_level 6;

HTTP/2 Push Critical Resources

// Express with HTTP/2 push
app.get("/", (req, res) => {
  // Push critical CSS
  if (res.push) {
    const cssStream = res.push("/css/critical.css", {
      request: { accept: "text/css" },
      response: { "content-type": "text/css" }
    })
    cssStream.end(criticalCSS)
  }

  res.send(html)
})

Common FCP Issues and Solutions

Issue 1: Web Font Loading Delays

Problem: Text remains invisible until fonts load

Solutions:

/* Use system fonts for critical text */
.hero-title {
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Load brand font for enhancement */
.hero-title.font-loaded {
  font-family: "Brand Font", sans-serif;
}
// Font loading API
if ("fonts" in document) {
  document.fonts.ready.then(() => {
    document.body.classList.add("fonts-loaded")
  })
}

Issue 2: Large CSS Bundles

Problem: Waiting for entire CSS file before rendering

Solution: Split and prioritize CSS

<!-- Critical inline CSS -->
<style>
  /* Minified critical CSS here */
</style>

<!-- Preload main CSS -->
<link rel="preload" href="/css/main.css" as="style" />

<!-- Load non-critical CSS -->
<link
  rel="stylesheet"
  href="/css/main.css"
  media="print"
  onload="this.media='all'; this.onload=null;"
/>

Issue 3: Third-Party Scripts

Problem: Analytics and ads blocking render

Solution: Defer third-party scripts

<!-- Load after window.load -->
<script>
  window.addEventListener("load", () => {
    // Load analytics
    const ga = document.createElement("script")
    ga.src = "https://www.google-analytics.com/analytics.js"
    ga.async = true
    document.head.appendChild(ga)

    // Load other third-party scripts
    setTimeout(() => {
      loadChatWidget()
      loadSocialWidgets()
    }, 2000)
  })
</script>

Issue 4: Slow Server Response

Problem: High TTFB delaying FCP

Solution: Implement caching and CDN

// Service worker for instant FCP on repeat visits
self.addEventListener("fetch", event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      // Cache hit - return response immediately
      if (response) {
        return response
      }

      // Cache miss - fetch and cache
      return fetch(event.request).then(response => {
        const responseClone = response.clone()
        caches.open("v1").then(cache => {
          cache.put(event.request, responseClone)
        })
        return response
      })
    })
  )
})

Issue 5: JavaScript-Dependent Content

Problem: Nothing renders until JavaScript executes

Solution: Progressive enhancement

<!-- Base HTML that renders immediately -->
<div class="product-list">
  <div class="product-skeleton">Loading products...</div>
</div>

<!-- Enhance with JavaScript -->
<script type="module">
  import { renderProducts } from "./products.js"
  renderProducts()
</script>

<!-- NoScript fallback -->
<noscript>
  <div class="product-list">
    <!-- Server-rendered product list -->
    <?php include 'products-static.php'; ?>
  </div>
</noscript>

Tools for Measuring FCP

Browser DevTools

Chrome DevTools Performance Panel:

// Programmatically measure FCP
new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.name === "first-contentful-paint") {
      console.log(`FCP: ${entry.startTime}ms`)
      // Send to analytics
      analytics.track("FCP", {
        value: entry.startTime,
        url: window.location.href
      })
    }
  }
}).observe({ entryTypes: ["paint"] })

Lighthouse

# Command line Lighthouse
lighthouse https://example.com --only-categories=performance

# Programmatic usage
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

async function measureFCP(url) {
  const chrome = await chromeLauncher.launch({chromeFlags: ['--headless']});
  const result = await lighthouse(url, {
    port: chrome.port,
    onlyCategories: ['performance']
  });

  console.log(`FCP: ${result.lhr.audits['first-contentful-paint'].displayValue}`);
  await chrome.kill();
}

Web Vitals Library

import { getFCP } from "web-vitals"

getFCP(metric => {
  console.log(`FCP: ${metric.value}ms`)
  console.log(`Rating: ${metric.rating}`) // 'good', 'needs-improvement', or 'poor'

  // Get detailed attribution
  const navEntry = performance.getEntriesByType("navigation")[0]
  console.log(`DNS: ${navEntry.domainLookupEnd - navEntry.domainLookupStart}ms`)
  console.log(`TCP: ${navEntry.connectEnd - navEntry.connectStart}ms`)
  console.log(`Request: ${navEntry.responseStart - navEntry.requestStart}ms`)
})

FCP Optimization by Platform

WordPress

// Inline critical CSS in WordPress
function inline_critical_css() {
  $critical_css = file_get_contents(get_template_directory() . '/critical.css');
  echo "<style>{$critical_css}</style>";
}
add_action('wp_head', 'inline_critical_css', 1);

// Defer non-critical scripts
function defer_scripts($tag, $handle) {
  $defer_scripts = ['jquery', 'main-script'];
  if (in_array($handle, $defer_scripts)) {
    return str_replace(' src=', ' defer src=', $tag);
  }
  return $tag;
}
add_filter('script_loader_tag', 'defer_scripts', 10, 2);

React Applications

// Code splitting for faster FCP
import { lazy, Suspense } from "react"

// Load immediately for FCP
const Header = () => <header>My App</header>
const LoadingSpinner = () => <div>Loading...</div>

// Lazy load heavy components
const Dashboard = lazy(() => import("./Dashboard"))
const Analytics = lazy(() => import("./Analytics"))

function App() {
  return (
    <div>
      <Header /> {/* Renders immediately for FCP */}
      <Suspense fallback={<LoadingSpinner />}>
        <Dashboard />
        <Analytics />
      </Suspense>
    </div>
  )
}

Frequently Asked Questions

What's the difference between FCP and LCP?

FCP measures when any content first appears (even a loading spinner), while LCP measures when the largest content element appears (usually the main content). FCP happens first and indicates the page is loading; LCP indicates the main content is ready.

Why is my FCP slower on mobile?

Mobile devices typically have:

  • Slower CPUs (3-5x slower JavaScript execution)
  • Higher network latency (especially on cellular)
  • Less memory for caching
  • Throttled performance on battery saver mode

Optimize specifically for mobile constraints.

Can a fast FCP hide a slow overall load?

Yes, this is called "progressive rendering." A fast FCP with skeleton screens or loading states improves perceived performance even if total load time is longer. However, don't sacrifice LCP for FCP—balance both metrics.

How do Single Page Applications affect FCP?

SPAs often have poor FCP because they must download, parse, and execute JavaScript before rendering anything. Use SSR, static generation, or progressive enhancement to improve SPA FCP.

Should I prioritize FCP or LCP?

Both are important, but LCP is a Core Web Vital (direct ranking factor) while FCP isn't. However, FCP strongly influences user perception and bounce rate. Optimize for both, but if forced to choose, prioritize LCP for SEO.

Put GEO into practice

Generate AI-optimized content that gets cited.

Try Rankwise Free
Newsletter

Stay ahead of AI search

Weekly insights on GEO and content optimization.