Fixing Render-Blocking CSS
A stylesheet linked in the head of a page stops the browser from painting anything until it has downloaded and parsed that file. With one small, fast stylesheet the delay is barely noticeable.
With
several files, a slow third-party font service, or an @import chain, visitors on mobile
data stare at a white screen, and your First Contentful Paint and Largest Contentful Paint suffer. This
guide is only about delivery: how to see which stylesheets block rendering, how to inline the few rules
the first screen needs, and how to load everything else without holding up the paint.
Why browsers wait for CSS
A browser builds a page from two trees: the DOM from your HTML and the CSSOM from your stylesheets. It cannot work out what anything looks like, or where it goes, until the CSSOM is complete for the styles that apply. Painting before then would mean showing raw, unstyled HTML and then redrawing it a moment later, which is the "flash of unstyled content" early browsers were known for. So modern browsers treat every stylesheet linked in the head, whose media condition matches the current screen, as render-blocking.
That rule is sensible, but it means the cost of each blocking stylesheet is added straight onto the time before the visitor sees anything. The cost is not just file size. Each file needs a request, and on a new connection that can mean a DNS lookup, a TCP handshake and a TLS negotiation before the first byte arrives. A stylesheet hosted on a different domain, such as a font service or a CDN you do not otherwise use, pays that connection cost on top.
Stylesheets also delay scripts. A classic script without async or defer
that appears after a stylesheet will not run until that stylesheet has loaded, because the script might
ask for computed styles. So a slow stylesheet can hold up JavaScript as well as painting.
What does not block
- A stylesheet whose
mediaattribute does not match, such asmedia="print", is still downloaded, but at low priority and without holding up the paint. - A stylesheet added to the page by JavaScript after load does not block the initial render.
- Rules inside a
<style>block in the head are already in the HTML, so there is no extra request to wait for, although the browser still has to parse them.
How to see which stylesheets are blocking
Start with the CSS Checker, which flags render-blocking stylesheets and measures file size, so you get a list of offenders for each template you test. Then confirm the picture in the browser, because what matters is the order and timing of requests on a real load.
PageSpeed Insights and Lighthouse
Run a mobile test. Recent versions of Lighthouse and PageSpeed Insights report this as a render-blocking requests insight; older versions call it "Eliminate render-blocking resources". Either way it lists each blocking file with its transfer size and an estimate of how long it held up the page. Treat the estimate as a guide to priority, not as an exact saving.
Chrome DevTools
- Open DevTools, go to the Network panel, tick Disable cache, filter by CSS and reload. Note every file requested before the first paint and where each one comes from.
- Open the Performance panel and record a reload with CPU and network throttling. Look for the first paint marker and see which stylesheet requests finish just before it. Those are the ones on your critical path.
- Paste this into the Console after a fresh load to list the files the browser itself marked as blocking:
performance.getEntriesByType('resource')
.filter(e => e.renderBlockingStatus === 'blocking')
.map(e => [e.name, Math.round(e.duration) + ' ms', e.transferSize + ' bytes']);
The renderBlockingStatus field is available in Chromium-based browsers. It includes blocking
scripts as well as stylesheets, so read the file names.
What a typical problem list looks like
| Blocking request | Usual cause | Fix covered below |
|---|---|---|
| Main theme stylesheet | Whole site's CSS loaded up front | Critical CSS plus deferred full file |
| Web font CSS from another domain | Font service link in the head | Self-host fonts or preconnect, then defer |
| A second file that only appears after the first loads | @import inside a stylesheet | Replace with a link or bundle it |
| Plugin or widget stylesheet | Loaded on every page, used on one | Load only where needed |
| Print or large-screen stylesheet | No media attribute | Add the correct media condition |
The fix, step by step
Step 1
List every stylesheet that blocks the first paint
Run the CSS Checker and a mobile PageSpeed Insights test on each main template, for example the home page, a product or service page and a blog post. Write down every stylesheet in the head with its origin, transfer size and whether it is yours, the theme's, a plugin's or a third party's. That list tells you which files you can change, which you can only move or remove, and which template benefits most from critical CSS.
Step 2
Remove @import chains and hidden extra requests
Search your stylesheets for @import. The browser only discovers an
imported file after it has downloaded and parsed the parent, so each import adds a full round trip before
rendering can start. Replace imports with ordinary <link> tags, or better, combine
the files at build time. Do the same for web font services: self-host the font files, or at least add
a preconnect hint for their domains so the connection starts early.
Step 3
Mark conditional stylesheets with a media attribute
If a stylesheet only applies in some conditions, say so in the link tag. Print
styles should carry media="print", and a file used only on wide screens can carry media="(min-width: 64em)".
The browser still downloads non-matching files, but at low priority and without delaying the paint. Better
still, move small conditional rules into the main file inside @media blocks so there is
one fewer request to make.
Step 4
Generate critical CSS for each template
Use a tool such as Critical, Penthouse or Beasties to extract the rules needed to draw the part of each template visible without scrolling. Run it at two viewport sizes, a phone and a laptop, because the first screen differs. Generate per template rather than per URL so the output stays manageable. Check the result by hand: remove rules for elements that are never above the fold, and add any states the tool could not see, such as an open cookie banner.
Step 5
Inline the critical rules in the head
Place the critical rules in an inline style block near the top of the head, before any link to the full stylesheet. Keep the block small, ideally well under about 14 KB after compression, so it arrives in the first few packets of the HTML. Minify it. If your site sends a Content-Security-Policy that restricts inline styles, add a nonce or hash for this block, otherwise the browser will ignore it and the page will render unstyled.
Step 6
Load the full stylesheet without blocking
Change the main link so it does not block, then switch it on once it has arrived. The
pattern below sets media="print" on the link, so the browser fetches it without waiting,
and a short script switches it to all when it finishes loading. Include a noscript copy
of the normal link for visitors without JavaScript. Keep blocking only the stylesheets that genuinely
cannot wait, and test that nothing jumps when the full file applies.
Step 7
Test the first paint on a throttled phone profile
Re-run the CSS Checker and PageSpeed Insights, then record a Performance trace in DevTools with slow 4G and CPU throttling. Confirm the first paint now happens before the full stylesheet finishes, that the first screen looks correct at that moment, and that Cumulative Layout Shift has not risen. Check the field data in Search Console's Core Web Vitals report over the following weeks, since lab tests only show one simulated visit.
A deferred loading pattern you can copy
This is a complete head for a template called service on a fictional site, Brightwell
Plumbing. The critical rules are inlined, the full stylesheet is fetched without blocking, and the font
domain is warmed up early.
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Emergency plumber in Leeds | Brightwell Plumbing</title>
<!-- 1. Rules for the first screen only, generated at build time -->
<style>
:root{--brand:#0b4f6c;--ink:#1d2327}
body{margin:0;font:1rem/1.5 system-ui,sans-serif;color:var(--ink)}
.site-header{display:flex;align-items:center;min-height:4rem;padding:0 1rem}
.hero{padding:2rem 1rem;background:var(--brand);color:#fff}
.hero h1{font-size:clamp(1.75rem,5vw,2.75rem);margin:0 0 .5rem}
</style>
<!-- 2. Full stylesheet: fetched without blocking, applied when loaded -->
<link rel="stylesheet" href="/css/site.4f2a9c.css" media="print" data-defer>
<script>
document.querySelectorAll('link[data-defer]').forEach(function (l) {
if (l.sheet) { l.media = 'all'; }
else { l.addEventListener('load', function () { l.media = 'all'; }); }
});
</script>
<noscript><link rel="stylesheet" href="/css/site.4f2a9c.css"></noscript>
<!-- 3. Print styles never block -->
<link rel="stylesheet" href="/css/print.css" media="print">
</head>
You will often see a shorter version of this that puts the switch in an inline load handler attribute on the link itself. It works the same way, but a strict Content-Security-Policy blocks inline handler attributes, so the small script block above, which can carry a nonce, is easier to keep working.
An alternative is rel="preload" with as="style", which fetches the file
at high priority without applying it, followed by the same switch to a real stylesheet. Preloading makes
sense when the full file is needed quickly for content just below the fold. For most small-business sites
the print-media pattern is simpler and harder to get wrong.
Platform notes
WordPress
Performance plugins such as WP Rocket, LiteSpeed Cache and Autoptimize can generate critical CSS and
load the remaining CSS asynchronously. Switch on one plugin's version of the feature, not two, and clear
its cache after theme changes, because stale critical CSS is a common cause of pages that briefly show
the wrong layout. Many plugin stylesheets are enqueued on every page; use wp_dequeue_style()
in a child theme to remove them from pages that do not use the feature. The WordPress
CSS guide covers theme and plugin stylesheets in more depth.
Shopify
Theme stylesheets are output in theme.liquid and in section files. Online Store 2.0 themes
already load many component stylesheets only in the sections that need them. App stylesheets injected
through content_for_header or app embeds are harder to control; if one blocks rendering
on every page, check the app's settings or ask the developer. The Shopify
CSS guide has the detail.
Static sites and frameworks
Add critical CSS generation as a build step, so it is rebuilt every time styles change. Next.js, Nuxt and Astro can inline or split CSS per route, and some offer an inlining option or plugin. Check the built HTML rather than assuming: open the page source and confirm the head contains the inline block and only the links you expect.
Edge cases and things that go wrong
- Stylesheets in the body. Chromium treats a stylesheet link placed in the body as blocking the rendering of content after it, not the whole page. Some themes rely on this to stage the load, but it is easy to create a visible pause halfway down the page. Test before relying on it.
- Critical CSS that goes stale. Critical rules are a copy. If they were generated once and never again, a redesign leaves old rules inline that fight the new stylesheet. Regenerate on every build or every theme change.
- Personalised or dynamic first screens. A logged-in header, a cookie banner or a sale bar may not be present when the tool renders the page. Add their rules to the critical set by hand if they appear on first view.
- Fonts. Inlining the
@font-facerules is fine, but font files still download separately. Usefont-display: swaporoptionalso text shows in a fallback font, and see the CSS layout shift guide if the swap moves text around. - HTTP/2 and many small files. HTTP/2 makes many requests cheaper, but each blocking stylesheet still has to arrive before the paint. Fewer blocking files is still better.
- Caching. Inlined CSS is re-sent with every HTML page and is not cached separately. That is why the block must stay small and the bulk of the rules must remain in a cacheable file with a versioned name.
Keeping the critical path short
Render-blocking CSS tends to creep back as plugins, apps and marketing tags are added. A few habits keep it under control:
- Keep a short record of which stylesheets each template is allowed to block on, and check new additions against it.
- Regenerate critical CSS automatically in your build or deployment, never by hand.
- Ban
@importin production stylesheets with a linter rule, so it cannot sneak back. - Re-run the CSS Checker after installing any plugin, app or widget, and after theme updates.
- Watch the Core Web Vitals report in Search Console for FCP and LCP changes that line up with deployments.
For how this work fits into the wider picture of loading speed, see the LCP guide and the Core Web Vitals guide.
1. Is all CSS render-blocking?
No. Only stylesheets that the browser needs before it can paint are render-blocking: those linked in the head whose media condition matches the current screen. A stylesheet marked with a media attribute that does not match, such as print, is still downloaded but does not hold up the first paint. Stylesheets added later by script do not block the first render either. Inline style blocks do not need a network request, although the browser still has to parse them before painting.
2. How big should critical CSS be?
As small as you can make it while the first screen still looks right. A common target is to keep the inline block well under about 14 KB after compression, because that amount can usually arrive with the first packets of the HTML response. Much larger and you are sending the same bytes again with every page view, since inline CSS is not cached separately. If your critical CSS is very large, the design of the first screen or the tool settings probably need a closer look.
3. Will deferring CSS cause a flash of unstyled content?
It will if you defer the stylesheet without inlining critical CSS first. The browser paints as soon as it can, and without any rules for the first screen it paints raw HTML. With good critical CSS in place, the first screen looks correct straight away and the full stylesheet only styles content further down or interactive states. Test on a throttled connection, where any gap between the first paint and the full stylesheet arriving is easy to see.
4. Does preload fix render-blocking CSS on its own?
Not by itself. Preload tells the browser to fetch a file early and at high priority, but it does not change whether a normal stylesheet link blocks rendering. If you add a preload and keep the ordinary blocking link, the file simply starts downloading slightly earlier. Preload helps when it replaces the blocking link as part of a pattern that applies the stylesheet once it has loaded, or when a file is discovered late, such as one referenced from another stylesheet.
5. Should I inline all my CSS instead?
Only for very small sites where the whole stylesheet is tiny. Inlining everything removes the request, but the CSS is then sent again with every page the visitor opens, and the browser cannot cache it. For most sites the better balance is to inline the rules for the first screen and keep everything else in an external file with a long cache lifetime and a versioned file name, so repeat visits and later pages load it from cache.
6. Why is a Google Fonts stylesheet flagged as render-blocking?
Because it is a stylesheet in the head from another domain. The browser has to connect to that domain, download the CSS, and only then can it paint. You can reduce the cost by adding preconnect hints for the font domains, but the most reliable fix is to self-host the font files and put the font-face rules in your own CSS or critical block. Remember that the font files themselves still download separately after the CSS.
7. Does fixing render-blocking CSS improve rankings?
It can help indirectly. Google uses Core Web Vitals as one of many page experience signals, and render-blocking CSS mainly affects First Contentful Paint and Largest Contentful Paint. A faster first paint also means fewer visitors give up before the page appears. But relevance and content quality matter far more, so treat this as part of making the site fast for people, not as a ranking trick. Measure field data before and after to see real effects.
The CSS Checker flags render-blocking stylesheets and measures stylesheet size, alongside validation errors, unused rules, specificity issues and prefix gaps, so you can see which CSS to deal with first.
Open CSS Checker Fixes โ