First Contentful Paint is the time from the start of navigation to the moment the browser draws the first piece of content — any text, image or non-white canvas. Before it, the visitor is looking at a blank screen. After it, they know the page is loading.
Google's threshold is 1.8 seconds or less for good, and more than 3 seconds for poor. It is not one of the three Core Web Vitals, but it sits directly upstream of the one that is hardest to pass, and the same faults delay both.
What counts as content
The first thing painted from the page's own DOM: text, an image, an SVG, or a canvas that is not blank. A background colour does not count. Nor does anything inside an iframe.
It does not have to be useful. A cookie banner, a loading spinner or a single line of header text all register as First Contentful Paint. That is the metric's main weakness, and the reason it has to be read alongside Largest Contentful Paint.
FCP and LCP
| First Contentful Paint | Largest Contentful Paint | |
|---|---|---|
| Marks | The first content of any kind | The largest element in the viewport |
| Tells you | When the blank screen ends | When the main content is there |
| Good | 1.8 s or less | 2.5 s or less |
| Core Web Vital | No | Yes |
LCP can never be earlier than FCP. So a slow FCP guarantees a slow LCP, and fixing FCP is the first move when both are failing.
The gap between them is diagnostic:
- Both slow, close together. The delay is before anything renders at all: the server, or resources blocking the first paint. This article.
- FCP fast, LCP slow. The page starts quickly and the main element arrives late, usually a large image or content inserted by JavaScript. That is a different problem with different fixes — see improving Largest Contentful Paint.
What happens before the first paint
Nothing can be drawn until all of this has finished, in order:
- Any redirects are followed.
- The server sends the first byte of HTML.
- The browser parses the HTML and finds the stylesheets and scripts in the
<head>. - Every render-blocking stylesheet is downloaded and parsed.
- Every synchronous script in the
<head>is downloaded and executed. - Text is painted, if its font is ready or a fallback is allowed.
Each step is a place FCP is lost. The Website Speed Test reports lab FCP in its transcript, next to the server response time for the same page, which is enough to tell the first two steps apart from the rest.
Cause 1: the server is slow to answer
Time to first byte is included in FCP in full. A server that takes 1.2 seconds to respond leaves 600 milliseconds for everything else, and no front-end work can win that back.
Check this first. Run the page through the Website Speed Test and compare the server response time with the FCP. If the response time is a large share of the FCP, start on the server: what a good TTFB is covers page caching, a CDN, and slow application code, in order of payoff.
Cause 2: redirects before the page
Every redirect is a full round trip spent before the HTML request even starts. An http:// address that redirects to https://, then to www, has used two round trips with nothing to show for it.
Trace the address visitors actually arrive on with the Redirect Checker. One hop is normal. More than that is time taken straight out of FCP.
Cause 3: render-blocking CSS
A stylesheet in the <head> blocks rendering until it has been downloaded and parsed. That is deliberate — painting unstyled content and then restyling it is worse — but it means every stylesheet is on the critical path, whether or not the page needs it yet.
- Remove what is unused. Themes and page builders routinely ship the CSS for every component on every page.
- Inline the critical CSS. The rules needed for the first screen go in a
<style>block in the HTML, so the first paint needs no extra request. The rest loads afterwards. - Fewer, smaller files. Minify, and avoid chains of
@import, where each file is discovered only after the previous one has downloaded. - Scope what is conditional. A print stylesheet with
media="print"does not block the screen render.
Cause 4: scripts in the head
A plain <script src> in the <head> stops the parser. The browser downloads the file, runs it, and only then carries on reading the page.
<script src="app.js" defer></script>
defer downloads in parallel and runs after the HTML is parsed, in order. async downloads in parallel and runs as soon as it arrives, in no guaranteed order. Either removes the script from the path to first paint. Use defer for anything that depends on the page or on other scripts, and async for independent ones such as analytics.
Third-party tags are the usual offenders: A/B testing snippets, tag managers, chat widgets and consent tools are often installed with instructions to place them as high in the <head> as possible. Each one is a request to someone else's server standing between your visitor and the first paint.
Cause 5: fonts that hide the text
By default, a browser that is waiting for a web font may paint nothing where the text should be, for up to about three seconds. If the first content on the page is text, that invisible period is counted in FCP.
@font-face {
font-family: "Brand";
src: url("/fonts/brand.woff2") format("woff2");
font-display: swap;
}
font-display: swap paints the text immediately in a fallback font and swaps when the web font arrives. Use optional if the swap itself is distracting: the web font is used only if it is already available.
Then make the font arrive sooner: serve it from your own domain, in WOFF2, and preload the one or two files used on the first screen.
<link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>
Cause 6: nothing in the HTML to paint
A page built entirely in the browser sends HTML containing an empty container and a script. There is nothing to paint until the JavaScript has downloaded, run, and often fetched its data as well.
The fix is to send real HTML: server-side rendering or static generation, so the first response already contains the content. Where that is not possible, an application shell in the HTML — header, layout, placeholder — gives the browser something to draw while the rest loads. That improves FCP honestly only if the shell is the real page structure and not a spinner.
A fast FCP that means nothing
Because any content counts, FCP is easy to improve without improving the page. A splash screen, a skeleton or a banner painted early produces an excellent number while the visitor waits just as long for what they came for.
Read the two metrics together. A good FCP with a poor LCP is not a fast page; it is a page that shows something quickly and then makes people wait. The number to act on is the one the visitor experiences as "it is here", which is LCP.
Lab and field
The speed test's lab figure comes from a single simulated load on a throttled connection. It is repeatable, which makes it the right number for checking whether a change helped.
What real visitors experienced is the field figure from Chrome's user data, reported at the 75th percentile over 28 days. It will differ from the lab number, sometimes widely, because real devices and networks vary and because returning visitors have much of the page cached. Core Web Vitals explained covers the distinction and why field data can lag a fix by weeks.
A working order
- Measure with the Website Speed Test and note the server response time and the lab FCP.
- If the server response is the larger share, fix that first.
- Remove redirects from the entry URL.
- Defer or remove scripts in the
<head>. - Cut and inline CSS.
- Set
font-displayand preload first-screen fonts. - Measure again after each change, one at a time.