What is First Contentful Paint (FCP)?
FCP (First Contentful Paint) measures how quickly a user sees the first visible element — such as text, an image, or a non-white canvas — after navigating to a page. It reflects the perceived load speed but does not guarantee that the full content is ready.
High FCP values can frustrate users, leading to increased bounce rates and poor UX. Improving FCP is crucial for perceived performance and SEO, as it’s part of the Core Web Vitals metrics used in Google’s rankings.
Common causes of poor FCP include:
- Large render-blocking JavaScript or CSS that prevents the browser from painting content.
- Slow server response or TTFB (Time To First Byte).
- Heavy fonts that aren’t preloaded, causing Flash of Invisible Text (FOIT).
- Unoptimized images loading too early or without dimensions.
- Excessive third-party scripts delaying the critical rendering path.
How FCP is Measured
FCP is the timestamp of when the browser first renders a piece of content from the DOM. This could be:
- Text (excluding script-injected text)
- Images (including background images)
- Canvas or SVG elements
To measure FCP with the web-vitals
library:
- import { onFCP } from 'web-vitals';
- onFCP(console.log);
Or natively with the Performance API:
- new PerformanceObserver((entryList) => {
- for (const entry of entryList.getEntries()) {
- if (entry.name === 'first-contentful-paint') {
- console.log('FCP:', entry.startTime);
- }
- }
- }).observe({ type: 'paint', buffered: true });
What is a Good FCP Score?
- Good: 1.8 seconds or less
- Needs improvement: 1.8 – 3.0 seconds
- Poor: Above 3.0 seconds

FCP should be measured at the 75th percentile across mobile and desktop users.
How to Improve FCP
Improving FCP involves streamlining the critical rendering path and showing the first visible content faster. Here are the most effective techniques:
1. Eliminate render-blocking resources
Move non-critical CSS or JS to load asynchronously using defer
or media
attributes.
- <link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
- <script src="script.js" defer></script>
2. Minify and compress CSS/JS
Use tools like NUglify
, Webpack
, or esbuild
to remove unnecessary code and reduce payload size.
- // Example with Webpack TerserPlugin
- optimization: {
- minimize: true,
- minimizer: [new TerserPlugin()]
- }
3. Preload key assets (fonts, hero images)
Use preload
for critical resources to reduce wait times during page render.
- <link rel="preload" href="//cdn.testdom.io/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>
- <link rel="preload" href="//cdn.testdom.io/images/hero.jpg" as="image">
4. Reduce server response time (TTFB)
Optimize backend performance, caching, and CDN strategies to deliver bytes faster.
- // Example: enable output caching in .NET
- [OutputCache(Duration = 300, VaryByParam = "none")]
- public ActionResult Index()
- {
- return View();
- }
5. Prioritize visible content
Inline critical CSS and avoid loading large resources before the first paint.
- <style>
- body { margin: 0; font-family: Inter; }
- .hero { height: 300px; background: #f2f2f2; }
- </style>
6. Monitor with Testdom.io
Use Testdom’s real-time Lighthouse scoring and historical tracking to catch regressions and optimize over time.
- // Sample Lighthouse FCP extract using Puppeteer
- const { lhr } = await lighthouse(url, options);
- console.log('FCP:', lhr.audits['first-contentful-paint'].displayValue);
By continuously optimizing your delivery pipeline and using automated insights from Testdom.io, you can keep FCP scores low and user satisfaction high.