Render-blocking resources are stylesheets and scripts in the document that the browser must download and process before it can paint any pixels on screen. They are one of the most common causes of poor First Contentful Paint (FCP) and Largest Contentful Paint (LCP) scores — and fixing them is often the fastest path to a 10–20 point Lighthouse improvement.
This guide explains exactly what render-blocking means, how to find the specific files slowing your site, and how to fix them on any stack.
What Are Render-Blocking Resources?
When the browser parses HTML, it builds the DOM top to bottom. When it encounters a or a without async or defer, it stops rendering and waits until that resource is downloaded and executed. Until that wait ends, the user sees a blank screen.
Lighthouse flags this under the audit "Eliminate render-blocking resources" and estimates the time savings in milliseconds if those resources were deferred or inlined.
| Resource type | Render-blocking by default? | Fix |
|---|---|---|
| CSS in | Yes | Inline critical CSS, defer the rest |
| without async/defer | Yes | Add defer or move to end of |
| | No (but can block paint indirectly) | Load only when needed |
| | No | Preferred for non-critical JS |
| Web fonts | Indirectly (FOIT/FOUT) | font-display: swap, preload |
| @import in CSS | Yes | Replace with tags |
On a typical WordPress or Shopify site, render-blocking resources account for 300–800ms of delay on mobile — enough to push LCP from "good" to "needs improvement."
1. Identify Render-Blocking Resources in Lighthouse
Run a Lighthouse audit on your URL. In the Opportunities section, find "Eliminate render-blocking resources." Each entry shows:
- The file URL (e.g.,
/assets/theme.css,jquery.min.js) - Estimated savings in milliseconds
- Whether it is a script or stylesheet
Export the full report from PageSpeed Exporter and look for audits with "id": "render-blocking-resources" in the issues array. The details.items field lists every blocking URL with its transfer size and duration.
wastedMs first. A single 200KB theme stylesheet blocking for 600ms has more impact than three 5KB utility scripts.
2. Defer Non-Critical JavaScript
Most tags in do not need to run before the page renders. Adding defer tells the browser to download the script in parallel but execute it only after HTML parsing completes.
<head>
<script src="/js/analytics.js"></script>
<script src="/js/chat-widget.js"></script>
</head>
After (non-blocking):
<head>
<script src="/js/analytics.js" defer></script>
<script src="/js/chat-widget.js" defer></script>
</head>
For scripts that must run as early as possible but should not block rendering, use async:
<script src="/js/urgent-polyfill.js" async></script>
Next.js: Scripts added via next/script with strategy="lazyOnload" or strategy="afterInteractive" are automatically non-render-blocking:
import Script from 'next/script';
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXX" strategy="afterInteractive" />
WordPress: Move plugin scripts from to footer using wp_enqueue_script with true as the last argument (loads in footer), or use a plugin like Asset CleanUp to defer specific handles.
3. Inline Critical CSS and Defer the Rest
CSS is trickier than JavaScript because the browser needs styles to render correctly — you cannot simply defer all CSS without causing a flash of unstyled content (FOUC).
The standard approach:
- Extract critical CSS — the minimum styles needed for above-the-fold content (typically 4–14KB)
- Inline it in a
tag in - Load the full stylesheet asynchronously using the media trick:
<head>
<!-- Critical CSS inlined -->
<style>
/* Above-the-fold styles: header, hero, typography */
body { margin: 0; font-family: system-ui, sans-serif; }
.hero { min-height: 60vh; background: #f8fafc; }
</style>
<!-- Full stylesheet loaded asynchronously -->
<link rel="preload" href="/css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
</head>
Next.js with Tailwind: Next.js automatically splits and optimizes CSS. Avoid importing large third-party CSS files in _app or layout.tsx unless necessary. Use @import sparingly — each import creates a render-blocking request chain.
WordPress: Plugins like WP Rocket and Autoptimize can automatically extract and inline critical CSS. Without a plugin, manually identify above-the-fold selectors using Chrome DevTools Coverage tab (Cmd+Shift+P → "Show Coverage").
4. Remove Unused CSS and JavaScript
Render-blocking is worse when the blocked resource is large. Lighthouse's "Reduce unused CSS" and "Reduce unused JavaScript" audits often overlap with render-blocking — the same files appear in both.
Common culprits:
| Platform | Typical unused CSS/JS source |
|---|---|
| WordPress | Full theme stylesheet + plugin bundles loaded on every page |
| Shopify | theme.scss.liquid + app embed scripts |
| React/Next.js | Entire component library imported instead of tree-shaken modules |
| Bootstrap/Tailwind | Full framework CSS when only 10% of utilities are used |
Fix for Tailwind: Ensure you are using PurgeCSS/content configuration so production builds only include classes you actually use. Fix for WordPress: Use Asset CleanUp or Perfmatters to disable plugin CSS/JS on pages where it is not needed. A contact form plugin loading its stylesheet on the homepage is a common 50KB render-blocking waste.5. Preconnect to Third-Party Origins
Render-blocking resources from third-party domains (Google Fonts, tag managers, chat widgets) pay an extra DNS + TLS handshake penalty. Add preconnect hints for origins you know will be requested:
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://www.googletagmanager.com">
</head>
Place preconnect tags before any resource from that origin. This does not eliminate render-blocking but reduces the time each blocking resource takes to start downloading.
6. Load Google Fonts Without Blocking
Google Fonts loaded via @import in CSS or a synchronous are render-blocking. The non-blocking pattern:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" media="print" onload="this.media='all'">
Always include &display=swap in the URL so text renders immediately with a fallback font while the web font loads — this also prevents layout shift from font swapping.
Before and After: Real-World Impact
I audited a Next.js marketing site with three render-blocking resources: a 180KB _app.css bundle, Google Tag Manager, and a synchronous Intercom script. Estimated blocking time: 740ms on mobile.
After applying:
deferon Intercom (loaded vianext/scriptwithlazyOnload)- GTM moved to
afterInteractive - Critical CSS already handled by Next.js
Your numbers will vary, but render-blocking fixes consistently deliver the highest ROI per hour of engineering time on content-heavy sites.
How to Verify the Fix
- Re-run a Lighthouse audit after deploying changes
- Confirm "Eliminate render-blocking resources" no longer appears (or shows reduced savings)
- Check FCP and LCP in the metrics section — both should decrease
- Use Chrome DevTools → Network tab → filter by CSS/JS and verify scripts show
(deferred)or load after DOMContentLoaded
For a before/after comparison on Starter plans, re-scan the same URL and use the comparison view on the results page.
Further Reading
- What is LCP? — render-blocking directly delays your LCP element
- What is FCP? — the first metric affected by blocking resources
- 5 Quick Wins to Improve Lighthouse Score — includes
fetchpriority="high"for LCP images - PageSpeed Exporter vs GTmetrix — comparing tools for AI-assisted fix workflows
- Use Lighthouse JSON with Cursor — feed your audit data to an AI agent for site-specific fixes