What is First Contentful Paint (FCP)?
First Contentful Paint (FCP) is a performance metric that measures the time until the browser renders the first piece of DOM content.
What is First Contentful Paint (FCP)?
First Contentful Paint (FCP) is a user-centric performance metric that marks the time from navigation start to when the browser renders the first bit of content from the DOM — text, an image, an SVG, or a non-white <canvas>. It's one of the six metrics tracked in Lighthouse (as of Lighthouse 12) and is reported in Chrome User Experience Report (CrUX) data, which Google uses for ranking signals. Google classifies FCP scores into three buckets: good (≤1.8 seconds), needs improvement (1.8–3.0 seconds), and poor (>3.0 seconds). FCP is distinct from Largest Contentful Paint (LCP), which tracks the largest visible element. A fast FCP matters because it gives users their first visual confirmation that the page is loading. We've found it's the single most impactful metric for perceived speed on content-heavy sites like blogs and landing pages.
How it works
FCP is measured by the browser's Paint Timing API (PerformanceObserver with entryTypes: ['paint']). When the rendering engine completes its first paint that includes DOM content — not just a background color — it fires a first-contentful-paint entry.
Here's how you can observe it in JavaScript:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime, 'ms');
observer.disconnect();
}
}
});
observer.observe({ type: 'paint', buffered: true });
The clock starts at navigationStart (or activationStart for prerendered pages) and stops when the browser paints the first content pixel. Several things block FCP:
- Render-blocking CSS and JS: Any
<link rel="stylesheet">or synchronous<script>in the<head>must be downloaded and parsed before the browser can paint. - Slow TTFB: If the server takes 1.2s to respond, your FCP floor is already 1.2s.
- Large DOM with no visible content above the fold: The parser has to reach paintable content.
- Web font loading: If text relies on a custom font that hasn't loaded and
font-displayisn't set toswaporoptional, the browser may delay text rendering.
In practice, improving FCP almost always means reducing the critical rendering path: fewer blocking resources, faster server response, and inlined critical CSS.
When to use it
FCP is the right metric to watch when you care about perceived load speed — how quickly a user sees something on screen.
Track FCP when:
- You're optimizing landing pages where bounce rate correlates with initial render speed
- You're diagnosing whether the bottleneck is server-side (TTFB) or client-side (render-blocking resources)
- You need a CrUX-reported metric that feeds into Google's page experience signals
- You're running A/B tests on critical CSS or font-loading strategies
FCP is less useful when:
- Your concern is interactivity — use Interaction to Next Paint (INP) instead
- You want to measure the main content's load time — LCP is more relevant
- You're building a single-page app where the shell renders instantly but meaningful content loads later (FCP will look great while the user stares at a spinner)
We default to tracking FCP alongside LCP and TTFB on every project. The three together tell you where time is being spent.
FCP vs alternatives
| Metric | What it measures | Good threshold | Best for |
|---|---|---|---|
| FCP | First DOM content painted | ≤1.8s | Perceived speed, first feedback |
| LCP | Largest visible element painted | ≤2.5s | Main content load |
| TTFB | Time to first byte from server | ≤800ms | Server/network performance |
| FP (First Paint) | Any pixel change (incl. background) | N/A (not in CWV) | Rarely useful alone |
| INP | Interaction responsiveness | ≤200ms | Interactivity after load |
FCP and LCP are the pair you need. FCP tells you when the page stops feeling blank. LCP tells you when it feels loaded. A big gap between FCP and LCP usually means your hero image or main text block is loading late — often a lazy-loading misconfiguration or a slow API call hydrating the main content.
Real-world example
On a recent Next.js 15 project (App Router, deployed on Vercel), our initial FCP was 2.4s on mobile 4G according to CrUX. We diagnosed the problem in three steps:
- TTFB was 650ms — acceptable, not the bottleneck.
- Two render-blocking CSS files totaling 89KB were in the
<head>. We inlined critical CSS (~4KB) and deferred the rest withmedia="print"+onloadswap. - A Google Font request was adding ~300ms. We switched to
next/fontwithfont-display: swapand self-hosted the subset.
Result: FCP dropped to 1.1s on the same connection profile. LCP followed, dropping from 3.1s to 2.0s. Bounce rate on the landing page decreased 14% over the following 30 days. We've repeated this pattern on 50+ projects — the fix is almost always render-blocking resources and fonts.