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
- Enable gzip/Brotli compression on your server
- Add
font-display: swapto all web fonts - Inline critical CSS (above-the-fold styles)
- Add
deferto non-critical scripts - 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
deferorasyncfor 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: swapto 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,
});
});