Skip to content
DERKONLINE

Stop Render-Blocking Resources From Stealing Your First Paint

Inline critical CSS, defer the rest, and load fonts without flashing so above-the-fold content paints in one round trip.

Derrick S. K. Siawor8 min read

There is a moment, right after the browser gets your HTML, where it wants to paint something for the user but cannot. It found a <link> to a stylesheet in the head, and the rules say it must download and parse that entire file before it draws a single pixel, because the CSS might change how everything looks. So the user stares at a blank white screen while a network request completes. That blank screen is render-blocking, and it is one of the most common reasons a fast server still produces a slow-feeling page.

The numbers say most of the web has this problem. According to the 2025 Web Almanac, only 15 percent of mobile pages pass the render-blocking audit, which means 85 percent are making users wait on resources that did not need to block. Clearing those blockers commonly recovers hundreds of milliseconds of First Contentful Paint, perceived speed bought back by changing how a few resources load rather than what they contain. Those hundreds of milliseconds are not cosmetic, since every slow second has a measurable cost in revenue. Here is how.

Why CSS blocks rendering at all

To understand the fix you have to understand why the browser is being so cautious. CSS is render-blocking by design, because the browser cannot safely paint content it has not yet styled. If it drew the page with no CSS and then the stylesheet arrived saying the header is hidden and the body is a different layout entirely, the user would see a jarring flash of unstyled content followed by a reflow. To avoid that, the browser waits: it will not render until it has the CSS it needs to render correctly.

The flaw in the default setup is that the browser waits for all your CSS, when it only needs a small slice of it to paint the part of the page the user can actually see. Your stylesheet might be 80 kilobytes covering every component on every page, but the content above the fold, the part visible without scrolling, depends on maybe 8 kilobytes of it. The browser blocks on the full 80 because it has no way to know which 8 it actually needs first. That gap, between the CSS that blocks and the CSS that matters for the first paint, is the entire opportunity.

Inline the critical CSS, defer the rest

The fix is to split your CSS into two parts and treat them differently. The critical part, the rules needed to render above-the-fold content, gets inlined directly into the HTML in a <style> tag in the head. The non-critical part, everything else, gets loaded asynchronously so it does not block the first paint.

Critical CSS inlined to paint above the fold in one round trip while full CSS, scripts, and fonts load non-blocking

Inlining the critical CSS means the browser has everything it needs to paint the visible page in the very first response, with no second round trip for an external file. There is no network request to wait on, because the styles arrived in the HTML itself. The browser parses the HTML, finds the inline styles, and paints immediately.

The rest of the CSS then loads without blocking. The common technique is to load the full stylesheet with a media trick that makes the browser treat it as non-blocking, then switch it on once it has arrived:

<link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">

The browser sees media="print", decides this stylesheet is not needed for screen rendering right now, and downloads it without blocking. When it finishes loading, the onload flips the media to all and the full styles apply. The user got their first paint from the inlined critical CSS, and the complete stylesheet arrived in the background to style everything below the fold and on interaction.

The work of identifying which rules are critical can be automated; tools extract the CSS actually used by above-the-fold content for a given page so you are not hand-curating it. The principle is what matters: give the browser exactly what it needs to paint the visible page immediately, and let the rest catch up.

Stop fonts from hiding your text

Fonts are the second render-blocking trap, and they fail in a particularly visible way. By default, when a browser is waiting for a custom web font to download, it hides the text that uses that font rather than showing it in a fallback. This is the Flash of Invisible Text, and on a slow connection it means your headline and body copy are simply absent until the font arrives, which can be a long time.

This hurts directly, because if your largest contentful element is text, its paint is held hostage to the font download. The fix is a single CSS declaration:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;
}

font-display: swap tells the browser to render the text immediately in a fallback font and swap in the custom font when it loads. The text appears right away, and the measurement of your contentful paint no longer waits on the font, which can meaningfully improve Largest Contentful Paint on text-heavy pages. If the largest element is an image rather than text, the companion move is making your LCP image load first every single time. The brief moment where the fallback font is visible before the swap is a far better experience than invisible text, and it keeps your paint metrics honest, though a poorly-sized swap can cause the layout to jump, which is why driving cumulative layout shift to zero belongs in the same pass.

Pair swap with preloading the one or two fonts that actually appear above the fold, so they start downloading early instead of being discovered late, and subsetting fonts to drop the glyphs you do not use, so the file the browser is waiting on is as small as possible. Smaller font, started earlier, with text visible the whole time.

JavaScript in the head blocks too

CSS and fonts are the usual culprits, but a <script> in the head with no async or defer attribute is render-blocking in the same way. The browser stops parsing the HTML, downloads and executes the script, and only then continues. If that script is large or slow, your entire page render waits on it.

The fix is to add defer to scripts that can run after the HTML is parsed, or async to scripts that are independent and can run whenever they arrive. defer is usually the right default for your own application scripts, because it preserves execution order and runs them after the document is ready, off the critical rendering path. The scripts most worth scrutinising here are the third-party ones, since taming the third-party scripts wrecking your page speed is often where the biggest blocking offenders live. Move what you can out of the blocking position, and the HTML parses and paints without waiting on JavaScript it did not need yet, the same goal as shrinking your Next.js bundle with server components that ship zero JS.

Verify against the critical path, not a vibe

These changes are easy to make and easy to get subtly wrong, so verify the result rather than assuming it, and verify it against real field data, not a single lab run. The metric to watch is First Contentful Paint, the moment the browser draws the first content, and Largest Contentful Paint, the moment it draws the largest above-the-fold element. Both are directly sensitive to render-blocking CSS, fonts, and scripts, so both should improve when you clear the blockers.

Use the browser's performance tooling to look at the critical request chain: the sequence of resources the browser must fetch before it can paint. After inlining critical CSS and deferring the rest, that chain should be dramatically shorter, ideally just the HTML and nothing else blocking the first paint. If you still see a stylesheet or a font or a script sitting in the blocking position, you have one more to clear. The goal is a critical path that lets above-the-fold content paint in a single round trip, with everything else loading around it.

Getting this right is foundational performance work, the kind that makes a site feel fast before any of the flashier optimizations matter, and it is part of how we build websites that load quickly on the real connections people actually use, not just on a fast laptop on a fast connection. A page that paints in one round trip feels instant; a page that blocks on 80 kilobytes of CSS and a hidden font feels slow no matter how good the server is.

The picture, painted fast

The end result of this work is a page that shows the user something useful almost immediately. The critical CSS is inlined, so the visible content paints from the first response with no blocking request. The fonts swap in a fallback instantly, so text is never invisible. The non-critical CSS and the scripts load around the first paint instead of in front of it. The critical request chain is short, and First Contentful Paint drops by hundreds of milliseconds.

None of this changed what the page contains. It changed the order things load in, so that the browser can paint what the user sees before it bothers with everything the user does not. That reordering is most of the difference between a page that feels slow and a page that feels instant, and it is sitting unclaimed on 85 percent of the web.