
In the first second after you open a page, the screen is blank, but the browser isn't idle. It starts parsing the HTML response as soon as it arrives, stops at every stylesheet link it encounters and doesn't paint a single pixel until it has downloaded and processed that file. This wait isn't a malfunction; it's by design: rather than putting an unstyled page on screen and fixing it seconds later, the browser waits until it's ready.
This is what determines how long the white screen lasts. A page's first view depends on how long the CSS takes to arrive, which font the text is waiting for and when the image on the first screen is discovered. These three look like separate topics, but they're all tied to the same chain. That chain is called the critical rendering path, and this guide covers all three of its links in one place.
What Is the Critical Rendering Path and What Does It Block?
The critical rendering path is the required sequence of steps the browser follows when turning the raw HTML from the server into pixels on the screen. HTML is parsed into the DOM, and CSS is parsed into the CSSOM. The browser needs a render tree to paint anything on screen, which is built by combining the two. In other words, the DOM alone isn't enough.
The result: CSS is a render-blocking resource by default, and the browser won't paint any of the content it has processed until the CSSOM is built. HTML being blocking is intuitive, because without the DOM there's nothing to paint. CSS being blocking is less intuitive. The browser does it to avoid briefly showing the user an unstyled page and then switching to the styled version right after. This situation, where unstyled content flashes on screen and disappears, is called FOUC, and browsers deliberately prevent it.
The practical consequence is a distinction most speed articles skip. Making a stylesheet non-render-blocking doesn't stop it from being downloaded. The media attribute only changes the blocking behavior; the browser downloads all CSS resources whether they're blocking or not.
<!-- Blocks rendering on screen -->
<link rel="stylesheet" href="https://seobaz.com/main.css">
<!-- Doesn't block rendering on screen, but is still downloaded -->
<link rel="stylesheet" href="https://seobaz.com/print.css" media="print">
This distinction matters because saving bandwidth and speeding up first paint are different problems. Playing with media solves the second, not the first. The real fix on the bandwidth side is to make the file smaller; we cover this in detail in our unused CSS and JS cleanup guide.
When Does Inlining Critical CSS Actually Help?
Critical CSS is the subset of CSS that's just enough to correctly paint the content visible on the page's first screen. Inlining means placing this subset directly inside the HTML as a style block instead of calling it from an external file. The goal is to eliminate an extra network request entirely: when the HTML arrives, the styles for the first screen have arrived too.
This technique is genuinely valuable, but only under a narrow condition. If your stylesheet loads noticeably later than the resource that makes up the page's largest content element, that element can't be painted even after its own download has finished. Looking at the network waterfall, you'll see the page still sitting blank even though the image is ready. In exactly this situation, moving the stylesheet inside the HTML removes the wait directly.
Equally important is knowing when the technique isn't worth it. Because inlined content is part of the HTML, it can't benefit from caching on subsequent page loads. While an external CSS file is downloaded once and reused across every page the visitor browses, an inline block is transferred again with every HTML response. So inlining is only recommended for small style sets. If your stylesheet is large enough to take longer to load than the LCP resource, that file isn't a good inlining candidate in the first place.
Google's own documentation describes this technique as an advanced performance method and says plainly that it can introduce errors if not implemented correctly. The same source notes that most sites can reach the recommended performance targets without applying this technique at all. This isn't a warning that dismisses the technique but a warning about order: critical CSS is the step at the end of the list, not the beginning.
In most cases, the healthier move is to slim the stylesheet down until it's smaller than the LCP resource. That way the file doesn't become a bottleneck for any visitor, and the caching advantage is preserved.
If you do go the inline route, the common pattern is this:
<head>
<style>/* only the rules that paint the first screen */</style>
<!-- the rest of the CSS loads without blocking rendering -->
<link rel="stylesheet" href="https://seobaz.com/main.css"
media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://seobaz.com/main.css"></noscript>
</head>
The media="print" trick here lets the file download without blocking rendering; once loading finishes, the media value is changed and the styles take effect. The noscript fallback is mandatory so the page doesn't stay unstyled if JavaScript is disabled.
The Hidden Maintenance Cost of Critical CSS
The real price of critical CSS is paid not at initial setup but afterwards. Accepting this from the start is better than dealing with a broken page six months later.
The first problem is the boundary itself. What we call above the fold has no universal pixel height, because the variety of devices and screens is nearly endless. A section that counts as above the fold on desktop requires scrolling on a narrow phone. So the extracted critical set is always an estimate: if the estimate is too narrow, you get a broken view on the first screen; if it's too broad, the inline block bloats.
The second problem is multiplication. The first screens of the homepage, category, product and article templates differ from one another. A single critical set doesn't fit them all, which means separate extraction and separate validation per template.
The third, and most insidious, is staleness. Every change made to the design invalidates the extracted critical set. If this extraction isn't tied to the build process, it goes stale without anyone noticing, and the page starts being painted with styles that don't belong to its current design. Open-source tools such as Critical and Penthouse automate the extraction, and some provide detailed control over whether font definitions are included, but none of them do the visual verification of the result for you.
| Situation | Right move |
|---|---|
| Stylesheet is small and arrives quickly | Leave it alone; there's nothing to gain |
| Stylesheet is bloated but full of unused rules | Clean it up first; don't inline |
| Stylesheet arrives later than the LCP resource and the first-screen set is small | A candidate for inlining critical CSS |
| Extraction can't be tied to the build process | Don't implement it; it will go stale |
| Site has many templates and the design changes often | Give it up unless you have automated extraction per template |
Why Text Appears Late: Block Period and Swap Period
The story doesn't end once the CSS is ready. If the page's largest element is a block of text, painting that text depends on the state of the web font, and browsers have two different behaviors here.
FOIT is when the browser keeps text completely invisible until the web font has downloaded. The layout forms and boxes settle into place, but the text areas are empty. FOUT is when the browser first shows the text in a system font and switches to the web font when it arrives. The user sees the text instantly and, in return, experiences a momentary visual change.
What governs this behavior is the font-display descriptor, which works through two periods. The block period starts the moment the browser requests the font; during this period, if the font isn't available, text is drawn with an invisible fallback, meaning the user can't see the text. The swap period comes after the block period; if the web font becomes ready within this window, it's swapped in.
| Value | Block period | Swap period | Practical result |
|---|---|---|---|
| auto | Varies by browser | Varies by browser | The browser decides; behavior is unpredictable |
| block | 2–3 seconds | Infinite | Text may stay invisible for seconds |
| swap | 0 ms | Infinite | Text appears instantly and is swapped even if the font arrives late |
| fallback | 100 ms | 3 seconds | If the font doesn't arrive within 3 seconds, the fallback stays for that load |
| optional | 100 ms | None | If the font doesn't arrive in time, it's never swapped in |
When no descriptor is written, auto applies and the strategy is left to the browser's discretion. On a page with a text-based LCP element, this is a source of delay you can't measure.
The Silent Side Effect of swap
The common advice points to font-display: swap, and it makes sense from a first-paint standpoint: the block period is close to zero, text appears immediately and the LCP counter doesn't wait for the font download. But swap's swap period is infinite. That means the web font will be swapped in even if it arrives very late. The gain and the risk come from the same property.
The risk is this: the fallback font and the web font have different character widths and line heights. When the swap happens, the space the text block occupies changes and the content below it moves. This shift, happening after the user has started reading, is recorded directly in the metric that measures layout shift. In other words, what you gain in text visibility you can lose in layout stability.
The way to solve this isn't to prevent the swap but to make it invisible. CSS's size-adjust, ascent-override, descent-override and line-gap-override descriptors bring the fallback font's metrics close to the web font's. When the fallback font occupies nearly the same space, there's nothing left to shift at the moment of the swap.
@font-face {
font-family: "Body";
src: url("/fonts/body.woff2") format("woff2");
font-display: swap;
}
@font-face {
font-family: "Body Fallback";
src: local("Arial");
size-adjust: 100%; /* calculated per font */
ascent-override: 100%; /* calculated per font */
descent-override: 100%; /* calculated per font */
line-gap-override: 0%;
}
body { font-family: "Body", "Body Fallback", sans-serif; }
The percentages here aren't constants you write by looking them up somewhere; they're calculated for each font pair and generated with tools that read font metrics. Values copied from another article often increase the shift rather than reduce it.
For sites that want to eliminate the shift risk completely, the second option is the optional value. Because it has no swap period, if the font isn't ready in time, it never kicks in during that page load, making swap-induced shift impossible. The price is that some visitors never see the brand's typography. There's no single right answer; the choice depends on how important the font is to the brand and how disruptive a late swap would be.
What Is Lazy Loading and Why Is There an Above-the-Fold Exception?
Lazy loading means deferring the loading of a resource until it's actually needed. At the browser level, this is done by adding the loading="lazy" attribute to an image, and the browser doesn't download that image until it's within a calculated distance of the viewport. The opposite value, eager, is already the browser's default behavior: the image is loaded wherever it is on the page.
<!-- images visible on the first screen -->
<img src="/product-1.avif" alt="..." width="400" height="400">
<img src="/product-2.avif" alt="..." width="400" height="400">
<!-- off-screen images -->
<img src="/product-7.avif" alt="..." width="400" height="400" loading="lazy">
<img src="/product-8.avif" alt="..." width="400" height="400" loading="lazy">
The reason for the exception lies in how the technique works. The browser can't decide whether to defer an image without knowing where that image will sit on the page. So the download of every image marked loading="lazy" waits for the layout calculation to progress. For an off-screen image, this wait is free, because there's time until the user scrolls there. For the image on the first screen, the opposite is true: there's nothing to gain, only time to lose. Google's explicit advice is not to apply lazy loading to images likely to be visible when the page loads, and especially not to the LCP image.
Lazy loading done with JavaScript libraries produces a more severe version of the same mistake. Because these libraries store the image's address in an attribute like data-src instead of src, the browser's preload scanner never sees the resource in the HTML response. The resource is only discovered after the script runs. The same invisibility problem applies to CSS background images: a background-image can't be discovered until the stylesheet is processed.
There are two different kinds of evidence about the size of this mistake, and they need to be kept apart. The first is correlational: in a large pool of pages based on real user data, the median page not using browser-level lazy loading had a 75th percentile LCP of 2,922 ms, while the median page using it measured 3,546 ms. This is a trend, not proof that the attribute alone causes the difference; sites that use lazy loading are typically more image-heavy. The second is causal but narrow in scope: a controlled test run with default lazy loading behavior switched on and off found median LCP on the desktop measurement of an archive page to be about 13 percent better with it switched off. Read together, the picture is clear: lazy loading applied in the wrong place causes measurable harm.
On the other hand, when applied in the right place, the technique is quite safe. In Chrome's experiments on Android, 97.5 percent of deferred images on a 4G connection were fully loaded within 10 ms of becoming visible; on slow 2G, the figure was 92.6 percent. So the problem isn't the technique itself but where it's applied.
Marking Up the LCP Image Correctly
For the largest image above the fold, the goal is simple: the browser should discover it as early as possible and download it at the highest possible priority. Three signals achieve this together.
<head>
<link rel="preload" as="image" href="https://seobaz.com/hero.avif" fetchpriority="high">
</head>
<body>
<img src="/hero.avif" alt="Product image"
width="1200" height="675"
loading="eager" fetchpriority="high">
</body>
loading="eager" is already the default, but writing it explicitly has practical value: many content management systems and themes automatically add lazy to every image with no value specified. An explicit eager overrides this automation.
fetchpriority="high" raises the resource's computed priority. In a documented experiment showing the effect of this attribute, giving the hero image of a large flight search page high priority brought LCP down from 2.6 seconds to 1.9 seconds. Because this is a measurement made on a single page, it should be read not as a universal gain rate but as an indicator of the technique's direction.
The default priority for image preloads is low or medium. That's why writing preload alone doesn't make an image urgent; what actually raises the priority is pairing it with fetchpriority. width and height do a separate job: they're needed so the browser can reserve space and the content below doesn't jump when the image arrives. Reducing the image's own weight is part of the same race; we cover the role of format choice in this equation in our using WebP and AVIF formats article.
Using Preload and Preconnect in Moderation
These two hints are powerful, and that's exactly why they can easily do harm. Both interfere with the browser's natural priority order; when used unnecessarily, they push important resources back.
preload exists for resources the browser would discover late on its own. Fonts defined inside a stylesheet, CSS background images and resources generated by scripts are typical examples. Preloading a resource that already sits plainly in the HTML and that the preload scanner sees on its first pass produces no gain; it only muddles the order. Placement matters too: font preloads generally work better placed at the end of the head or the start of the body, while preloads piled at the very top of the HTML can jump ahead of more critical resources.
preconnect only makes sense for domains other than your own; writing a preconnect to your own origin has no effect. The browser closes a connection that isn't used within 10 seconds, so connections opened on speculation go to waste. Because fonts are downloaded in anonymous mode, crossorigin is required on a hint pointing to an origin that serves fonts.
<link rel="preconnect" href="https://fonts.example.com" crossorigin>
<link rel="preload" as="font" type="font/woff2"
href="https://seobaz.com/fonts/body.woff2" crossorigin>
A practical rule: limit the preconnect list to third-party origins that actually do work in the first second, and don't push preload beyond a handful of resources per page.
Which Technique Affects Which Metric
The main reason these techniques get confused is that they're all lumped together under "speed" and assumed to do the same thing. In fact, LCP consists of four sub-parts: the time for the server to send the first byte, the delay in discovering the resource, the time to download the resource and the delay in rendering the element. Each technique touches only one of these parts.
| Technique | LCP part it affects | Side effect risk |
|---|---|---|
| Inlining critical CSS | Element render delay | Loss of caching and maintenance burden |
| Shrinking the stylesheet | Element render delay | Practically none |
| eager and fetchpriority on the LCP image | Resource load delay | Backfires if applied to the wrong image |
| Preloading the LCP image | Resource load delay | Too many preloads create network contention |
| preconnect | Third-party connection time | Wasted unused connections |
| font-display strategy | Render delay for text LCP | A late swap causes layout shift |
| Below-the-fold lazy loading | Resource load duration (reduces contention) | Hurts LCP in the wrong place |
The takeaway from the table is that shortening a single part isn't guaranteed to shorten the total time. If you reduce resource download time but the element is kept hidden until JavaScript finishes, the time you gained shifts straight into render delay and LCP doesn't change. That's why measurement should be done on the whole, not part by part; we detail the metric's thresholds and how to read it in our guide to getting LCP under 2.5 seconds.
Order of Implementation
You don't need to apply every technique in this guide at once, and generally you shouldn't. Starting with what's cheap and has a guaranteed payoff and moving toward what's expensive and fragile produces both less risk and clearer measurement.
- Measure. Any intervention made without knowing which element is the LCP and which resource is blocking rendering is guesswork.
- Remove lazy loading from the images on the first screen and add
widthandheightto all images. This is the cheapest and most common win. - Clean up unused CSS and JS. Keeping the stylesheet smaller than the LCP resource may make inlining unnecessary.
- Choose a font strategy:
swapwith a metric-matched fallback, oroptionalif layout stability is the priority, and preload the font. - Mark up the LCP image with
eagerandfetchpriority="high", and add a preload if needed. - Define a limited number of preconnects for third-party origins that actually do work in the first second.
- If render delay still dominates after all this, inline critical CSS and tie the extraction to the build process.
Frequently Asked Questions
Can I write critical CSS by hand?
Technically you can, but it isn't sustainable. A hand-written critical set becomes invalid at the first design change, and nobody notices, because the page keeps opening; it just looks wrong. If you can't tie extraction to the build process, it's safer not to implement this step at all.
Would it be faster if I inlined all of my CSS?
Generally no. When you embed all styles in the HTML, every page the visitor browses downloads the same styles again, because inline content can't be cached. A large inline block also bloats the HTML response and delays processing of the first byte. It's defensible on single-page campaign pages, but not on multi-page sites.
Can lazy loading be applied to iframes too?
Yes, loading="lazy" has also been standardized for the iframe element. Video embeds, maps and social media widgets usually sit far below the fold and load a significant amount of resources, which makes them the most efficient lazy loading candidates.
What happens if I don't define font-display?
auto applies and the strategy is left to the browser. The block and swap periods vary by browser, so you can't predict how long text will stay invisible. On pages with a text-based LCP element, this is an uncontrolled variable.
How do I determine the above-the-fold boundary?
There's no fixed pixel value, and looking for one is the wrong question. The practical approach is to treat whatever is visible in the first view on a narrow mobile screen as above the fold and leave that set eager. When in doubt, the cost of leaving one extra image eager is much lower than the cost of accidentally deferring the LCP image.



