DEV Community

loading...
Cover image for Using First Contentful Paint - FCP
Request Metrics

Using First Contentful Paint - FCP

Todd H. Gardner
Aspiring digital comedian & occasional JavaScript developer. I cofounded TrackJS produce PubConf, and host ScriptAndStyle.
Originally published at requestmetrics.com on ・4 min read

First Contentful Paint, or FCP, measures the time take to render the first element of a webpage. It’s a modern, user-centric measurement of how fast users see a response from your website. Here’s everything you need to know about the metric and how to use it.

FCP is one of the Core Web Vital performance metrics that measure user’s perceived performance of websites. These metrics are incredibly important to delivering fast user experiences, and avoiding SEO performance penalties. Check out how FCP compares with other performance metrics in The Definitive Guide to Measuring Web Performance.

First Contentful Paint Metric

The First Contentful Paint metric considers all the steps required to show the user the first bit of requested content. This includes the time for your servers to respond, network transfer time, HTML size and complexity, and required CSS assets.

In this case, Contentful refers to any text or image elements, but not the background color of the document.

Check out this example waterfall chart , and where FCP is marked.

FCP in the Loading Waterfall
FCP in the Loading Waterfall

Loading webpage images is not needed for FCP, but the browser does need to download all the CSS styles, including styles chained with @import statements. Nesting multiple levels of styles can dramatically slow down your FCP performance.

FCP Metric Range
FCP Metric Range

Google defines the acceptable ranges for FCP to be less than 1.0 seconds. Anything over that risks a negative user experience and a possible ranking penalty. FCP scores larger than 3.0 seconds indicate a serious performance problem for your website.

Measuring FCP with PerformanceObserver

First Contentful Paint is measured using the PerformanceObserver API and is supported in Blink-based browsers, such as Chrome, Edge, Android, and Opera. Other browsers, including Chrome on iOS, Safari, and Firefox, cannot report FCP metrics.

Here’s a little code to illustrate the API:

new PerformanceObserver((entryList) => {
    console.log(entryList.getEntriesByName("first-contentful-paint"));
}).observe({ type: "paint", buffered: true });
Enter fullscreen mode Exit fullscreen mode
Example of First Contentful Paint API

Unlike the Largest Contentful Paint metric, there is not a dedicated type for FCP. You must listen to all paint events and filter them by name. The buffered option allows you to gather data after it has happened. Paste in that code into DevTools on any page and you’ll see something like this:

FCP Performance Entry
FCP Performance Entry

Note that the measurement value is startTime, not duration. startTime is when the FCP event started, while duration is how long the event lasted, which is usually 0.

Quirks, Gotchas, and Unexpected Behavior

Likely, you won’t be measuring FCP yourself. You’ll rely on a library like web-vitals or a service like Request Metrics to gather the data for you. But if you do want to measure FCP yourself, there are a few unexpected things you should look out for.

1. Don’t Measure Background Pages

Pages that are loaded in the background, such as opened in another tab or while minimized, will not have accurate FCP measurements. The first-contentful-paint performance entry is fired once the page is viewed, which will be significantly slower than the actual loading time of the page.

You can detect whether a page is in the background and filter out metrics:

var hiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;

document.addEventListener('visibilitychange', (event) => {
    hiddenTime = Math.min(hiddenTime, event.timeStamp);
}, { once: true });

new PerformanceObserver(entryList => {
    entryList.getEntriesByName("first-contentful-paint").forEach((entry) => {
        if (entry.startTime < hiddenTime) {
            // This entry occurred before the page was hidden
            console.log(entry);
        }
    };
}).observe({ type: "paint", buffered: true });

Enter fullscreen mode Exit fullscreen mode
Don't Background Pages

2. Always Use Feature Detection

Many (most) browser do not support PerformanceObserver or the paint type. Some browsers, the object is not defined, while others will throw an error if you attempt to use it. Try/catch is the most reliable way to detect browser compatibility.

try {
    new PerformanceObserver(entryList => {
        console.log(entryList.getEntriesByName("first-contentful-paint"));
    })
    // Some browsers throw when 'type' is passed:
    .observe({ type: "paint", buffered: true });
}
catch (e) { /* Not Supported */ }
Enter fullscreen mode Exit fullscreen mode
Detect Largest Contentful Paint API with try/catch

3. Styles to the Document Don’t Count

Some pages will apply inline styles to their html or body elements, such as background-color, borders, or outlines. While these styles do paint to the screen, they are not considered for the first-contentful-paint metric.

Improving Your FCP Scores

FCP includes all the time waiting and fetching the document, CSS files, and synchronous scripts. You can improve your FCP scores by making your servers quick, your resources small and few, and the network hops short. Here are some common tactics that can help:

  • Reduce server work and cache expensive actions
  • Use compression on your HTML, CSS, and other resources
  • Reduce the critical CSS required to render the page
  • Use fewer and more efficient fonts
  • Serve content through a CDN
  • Use efficient http caching settings

All the improvements that you make to FCP will also help your Largest Contentful Paint scores.

Conclusion

The First Contentful Paint metric is an important part of the web performance story, but it’s not the only one! Learn more about the other web performance metrics and how to measure them in the Definitive Guide to Measuring Web Performance.

Discussion (0)