/ โ† Core Web Vitals Fixes / First Contentful Paint
In this guide
  1. What counts as the first contentful paint
  2. What must happen before anything is painted
  3. Finding your blocker
  4. Critical CSS in practice
  5. Scripts, hiding snippets and fonts
  6. Platform notes
  7. The fix, step by step
  8. Keeping the first paint fast
  9. Questions and answers

What counts as the first contentful paint

The browser records FCP the first time it draws any content from the page's DOM. Text counts, as long as it is actually visible. So do images, including CSS background images, SVG elements and canvas elements that have been drawn on. A plain background colour does not count, and neither does text that the browser is holding back while a web font downloads.

Google's guidance rates FCP like this, using the 75th percentile of visits:

FCPRating
1.8 s or lessGood
Over 1.8 s up to 3 sNeeds improvement
Over 3 sPoor

FCP appears in the Chrome UX Report and in PageSpeed Insights, but it is a diagnostic measurement. Search Console's Core Web Vitals report is based on LCP, INP and CLS, not FCP. The reason to fix FCP is what it tells you: if nothing at all appears for three seconds, the largest element cannot possibly appear within 2.5, and visitors on slow connections may leave before they see anything.

๐Ÿ’ก Look at the gap between FCP and LCP. If they are close together, the same thing is holding both back, usually the server or blocking files in the head. If FCP is quick but LCP is much later, the problem is specific to the hero element, and the LCP guide is the better place to start.

What must happen before anything is painted

Between the click and the first paint, the browser works through a fixed sequence. Every item on this list adds to FCP, and any one of them can be the slow part:

  1. Redirects. Each hop, for example from an advert's tracking link to http, then https, then the www version, is a full round trip before your page is even requested.
  2. Connection and server response. DNS lookup, connection set-up, TLS, and then the time your server takes to produce the HTML. Together these make up Time to First Byte.
  3. Downloading and parsing the HTML head. The browser reads the head from top to bottom. A very large head, packed with inline scripts, tracking tags and inline SVG sprites, takes longer to arrive and to read.
  4. Render-blocking stylesheets. Every <link rel="stylesheet"> that applies to the current screen must be downloaded and parsed before anything is drawn. Stylesheets pulled in with @import are worse, because the browser only finds them after reading the first file.
  5. Parser-blocking scripts. A <script src> without defer or async in the head stops the browser reading the HTML until the script has downloaded and run.
  6. Something visible to paint. If the body is empty until JavaScript builds it, or is hidden by a style rule, or all its text is waiting for a font, the browser has nothing to show even when it is ready.

A fast site keeps this path short: one quick response, one small stylesheet or inlined critical rules, no blocking scripts, and real text in the HTML.

Finding your blocker

Run the page through the Core Web Vitals checker and note FCP from the field data and the lab run, alongside TTFB. Subtract TTFB from FCP. What remains is time the browser spent after the HTML started arriving, and that is time you can usually win back in the front end.

In Chrome DevTools

  1. Open the Network panel, tick Disable cache, and set throttling to a slow 4G profile.
  2. Reload and look at the first few requests. Redirects show as 301 or 302 rows before the document. Look for stylesheets and scripts requested from the head, and note which have Highest or High priority.
  3. Switch to the Performance panel and record a reload. The FCP marker appears in the Timings track. Everything on the main thread and the network before that marker is a candidate blocker.
  4. Open the Coverage tab from the command menu and reload. It shows how much of each CSS and JavaScript file was actually used for the first view. A 300 KB stylesheet with 90% unused code is a strong sign that critical CSS will help.

Lighthouse lists the specific offenders in its render-blocking diagnostics, with an estimate of the time each one costs. Treat the estimates as a guide to priority rather than an exact saving.

A quick test for hidden content

Load the page with JavaScript switched off in DevTools settings. If you get a blank screen, the page relies on scripts to show anything, and FCP can never be earlier than the time those scripts take to download and run. If you see content but it is invisible when JavaScript is on, look for a hiding snippet.

Critical CSS in practice

Critical CSS means the small set of rules needed to draw the first screenful: the header, navigation, hero area and the typography of the opening text. Put those rules inline in the head, then load the full stylesheet without blocking rendering.

<head>
  <style>
    /* critical rules for header, hero and first text block only */
    body{margin:0;font-family:system-ui,sans-serif;color:#1d2433}
    .site-header{display:flex;align-items:center;height:64px;padding:0 16px}
    .hero h1{font-size:2rem;line-height:1.2;margin:24px 16px}
  </style>
  <link rel="preload" href="/css/site.css" as="style">
  <link rel="stylesheet" href="/css/site.css" media="print" id="site-css">
  <script>
    const css = document.getElementById('site-css');
    css.addEventListener('load', () => { css.media = 'all'; });
  </script>
  <noscript><link rel="stylesheet" href="/css/site.css"></noscript>
</head>

The media="print" trick makes the browser fetch the stylesheet at low priority without blocking the screen render, and the tiny script switches it on for screens once it has loaded. The noscript fallback keeps the page styled for visitors without JavaScript. Keep the inline block small, ideally a few kilobytes, because inlined CSS cannot be cached between pages.

โš ๏ธ Getting critical CSS wrong can cause a flash of unstyled content or layout shift when the full stylesheet arrives. Test the first screen on mobile and desktop after every change, and generate the critical rules per template rather than once for the whole site.

Scripts, hiding snippets and fonts

Scripts in the head

<!-- blocks parsing and rendering -->
<script src="/js/app.js"></script>

<!-- downloads in parallel, runs after the HTML is parsed, in order -->
<script src="/js/app.js" defer></script>

<!-- downloads in parallel, runs as soon as it arrives, order not kept -->
<script src="https://analytics.example.com/tag.js" async></script>

Use defer for your own scripts that depend on each other, and async for independent third-party tags. Modules loaded with type="module" are deferred by default.

Anti-flicker and page-hiding snippets

Some A/B testing and personalisation tools ask you to add a snippet that hides the whole page, often by setting the body's opacity to zero, until the experiment script has loaded, with a timeout of several seconds as a safety net. On a slow connection the visitor stares at a blank screen for the full timeout. If you must keep such a tool, shorten the timeout, hide only the element being tested rather than the whole page, or run experiments on the server so no hiding is needed.

Web fonts

By default, most browsers hide text for a short period while a web font downloads. If all the visible content is text in that font, nothing counts as painted. Set font-display: swap so fallback text is drawn at once, preload the one or two font files used in the first screen, and self-host fonts so they come from the same connection as the page.

@font-face{
  font-family:"Harbour Sans";
  src:url("/fonts/harbour-sans-600.woff2") format("woff2");
  font-weight:600;
  font-display:swap;
}

Platform notes

The fix, step by step

Step 1

Measure FCP and separate server time

Test the page in the Core Web Vitals checker and write down field and lab FCP next to Time to First Byte for mobile. Subtract TTFB from FCP to see how much time the browser spends after the HTML starts arriving. If TTFB alone is over about 800 ms, or the page goes through redirects before loading, fix the server and the redirect chain first, because no front-end change can paint content before the HTML arrives.

Step 2

List what blocks the first render

Reload the page with the Network panel open, cache disabled and slow 4G throttling on. Write down every stylesheet and every script without defer or async requested from the head, and any @import chains. Use the Coverage tab to see how much of each file the first screen actually needs. This list, not a generic checklist, is what you work through in the next three steps, starting with the largest items.

Step 3

Inline critical CSS and load the rest later

Extract the rules needed for the header, navigation, hero and opening text of each template and put them in a small inline style block in the head. Load the full stylesheet with the print-media pattern or a preload so it no longer blocks rendering. Replace @import with link tags, add media attributes to print-only files, and test for unstyled flashes on mobile and desktop.

Step 4

Move scripts out of the way

Add defer to your own scripts in the head, or move them to the end of the body, and load independent third-party tags with async. Remove scripts that no longer do anything, such as old chat widgets or retired analytics. If a script genuinely must run before paint, inline it if it is tiny, and check whether the work could be done on the server instead.

Step 5

Remove hide-the-page snippets

Search the page source for styles that set the body or a wrapper to opacity:0 or visibility:hidden, often added by A/B testing, personalisation or fade-in effects. Remove them if the tool is no longer in use. Otherwise cut the timeout to the minimum, hide only the element under test, or move experiments to the server so visitors are never shown a deliberately blank page while a script loads.

Step 6

Let text show while fonts load

Add font-display: swap to every @font-face rule, or the equivalent parameter for hosted font services, so fallback text is drawn immediately. Preload only the one or two font files used above the fold, self-host them on your own domain, and choose a fallback font with similar proportions. Check the result with slow throttling, where the difference between hidden and visible text is easiest to see.

Step 7

Send real content in the HTML

View the source of the page and check that the header and opening text are present as HTML rather than built by JavaScript. For apps that render only in the browser, switch key templates to server-side rendering or static generation. Then rerun the lab test several times, confirm FCP has moved closer to TTFB, and watch field data over the following four weeks.

Keeping the first paint fast

For deeper coverage of the files themselves, see the render-blocking resources guide, and for the server side, the Time to First Byte guide.

Questions and answers

1. Is First Contentful Paint a Core Web Vital?

No. The Core Web Vitals are Largest Contentful Paint, Interaction to Next Paint and Cumulative Layout Shift. FCP is a supporting measurement that Google reports in the Chrome UX Report and PageSpeed Insights, and Lighthouse includes it in its lab score, but it is not part of the Core Web Vitals assessment in Search Console. It still matters, because a slow first paint usually means a slow LCP, and a blank screen is when visitors are most likely to give up.

2. What is a good FCP time?

Google rates an FCP of 1.8 seconds or less as good, between 1.8 and 3 seconds as needing improvement, and over 3 seconds as poor, measured at the 75th percentile of real visits. Because FCP includes redirects and server response time, a page on slow hosting can miss the target however tidy its front-end code is. Compare FCP with Time to First Byte to see how much of the delay is under your front-end control.

3. Why is my page blank for several seconds before anything appears?

The browser is waiting for something before it will draw. The usual suspects are a slow server response or redirects, stylesheets and synchronous scripts in the head, a page-hiding snippet from an A/B testing tool, text held back while a web font loads, or a page that is built entirely by JavaScript. Load the page with JavaScript switched off and with slow throttling in DevTools to see which of these applies to your site.

4. Does async or defer make a difference to FCP?

Yes, often a large one. A plain script tag in the head stops the browser reading the rest of the HTML until that script has downloaded and run, so nothing can be painted in the meantime. Adding defer lets the file download in parallel and run after the HTML is parsed, keeping the order of scripts. Adding async also downloads in parallel but runs the script as soon as it arrives. Either removes the script from the path to the first paint.

5. Is inlining all my CSS a good idea?

Only for small sites with very little CSS. Inlining removes a network request, which helps the first paint, but inline CSS is sent again with every page and cannot be cached by the browser. For most sites the better pattern is to inline only the few kilobytes needed for the first screen and load the full stylesheet without blocking rendering. That keeps the first view fast and later pages light because the main file is cached.

6. Can a cookie banner slow down FCP?

It can, depending on how it is built. A consent tool loaded as a synchronous script in the head blocks the first paint until it arrives, and some banners hide the page until a choice is made. A well-built banner loads asynchronously and draws on top of content that is already visible. The banner itself can count as the first content painted, which is fine, as long as the script behind it does not delay everything else.

7. Why does Lighthouse show a different FCP from the field data?

Lighthouse loads the page once with an empty cache, on a simulated mid-range phone and a throttled connection. Real visitors include people with the site already cached, people on fast broadband, and people on poor mobile signal, and field data takes the 75th percentile over 28 days. The two often differ by a second or more. Use the lab figure to test changes and the field figure to judge what visitors experience.

See what is delaying your first paint

The Core Web Vitals checker shows real-user field data from the Chrome UX Report and a Lighthouse lab test side by side for your URL, and points to the assets and code behind each slow result.

Open Core Web Vitals Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All Core Web Vitals Fixes  ·  Slow Largest Contentful Paint  ·  Render-Blocking Resources  ·  Slow Time to First Byte  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.