Render-Blocking Resources
Waiting for the entire buffet to be set up before anyone can eat? That's what render-blocking scripts and styles do to your page.
What is this?
Picture a buffet where no one is allowed to eat until every single dish — appetizers, mains, desserts, the chocolate fountain — is perfectly arranged. That is what render-blocking resources do to your webpage. When the browser encounters a <script> or <link> tag in your HTML, it stops everything to download and execute that file before it paints a single pixel. Your visitors stare at a blank screen waiting for JavaScript and CSS they may not even need yet.
Why it matters
- For your visitors: They clicked a link expecting content. Instead they get a white screen for 2, 3, sometimes 5 seconds while the browser downloads and processes files. On mobile networks, this is agonizing. Many visitors will simply hit the back button and try someone else's site.
- For your business: First Contentful Paint (FCP) — how quickly visitors see something — is a Core Web Vital. Render-blocking resources are the number one killer of FCP. Google measures this and uses it in search rankings. A slow FCP means fewer visitors stay, fewer convert, and fewer come back.
- The standard: Critical CSS should be inlined or loaded with high priority. Non-critical CSS should be deferred. JavaScript should use
asyncordeferattributes so it does not block rendering. Only the absolute minimum needed for the first paint should block.
<head>
<!-- Critical CSS loads normally -->
<link rel="stylesheet" href="/critical.css" />
<!-- Analytics: async — loads in parallel, executes when ready -->
<script src="/analytics.js" async></script>
<!-- App bundle: defer — loads in parallel, executes after HTML parsing -->
<script src="/app.js" defer></script>
</head><head>
<link rel="stylesheet" href="/critical.css" />
<link rel="stylesheet" href="/animations.css" />
<link rel="stylesheet" href="/print.css" />
<script src="/analytics.js"></script>
<script src="/app.js"></script>
<script src="/chat-widget.js"></script>
</head>How to fix it
React / Next.js
Next.js handles most of this automatically with its Script component and built-in CSS optimization. But third-party scripts are where things go wrong.
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
{/* Analytics: loads after page is interactive */}
<Script src="https://analytics.example.com/script.js" strategy="afterInteractive" />
{/* Chat widget: loads when browser is idle */}
<Script src="https://chat.example.com/widget.js" strategy="lazyOnload" />
</body>
</html>
);
}For CSS, dynamically import heavy stylesheets so they do not block the initial render:
// Only load animation styles when needed
"use client";
import { useEffect } from "react";
function AnimatedSection() {
useEffect(() => {
import("@/styles/animations.css");
}, []);
return <div className="animated-section">Content</div>;
}Plain HTML
Use async and defer on scripts, and media queries to prevent non-critical CSS from blocking.
<head>
<!-- Critical CSS — blocks rendering (that's OK, it's needed) -->
<link rel="stylesheet" href="/critical.css" />
<!-- Print styles — only blocks rendering for print -->
<link rel="stylesheet" href="/print.css" media="print" />
<!-- Non-critical CSS — loads without blocking -->
<link rel="stylesheet" href="/animations.css" media="print" onload="this.media='all'" />
<!-- Scripts that don't need to block -->
<script src="/analytics.js" async></script>
<script src="/app.js" defer></script>
</head>The difference between async and defer: both download in parallel without blocking, but async executes immediately when downloaded (potentially out of order), while defer waits until the HTML is fully parsed and executes in order. Use defer for your app code and async for independent scripts like analytics.
Render-blocking resources are the number one reason pages feel slow. The cat does not wait in line, and neither should your visitors.
How the cat scores this
The scanner identifies all <script> and <link rel="stylesheet"> tags in the <head> and checks whether they block rendering. Scripts without async, defer, or type="module" are flagged. Stylesheets without appropriate media attributes that are not critical for above-the-fold content are noted. The scanner also measures the total size of render-blocking resources — bigger payloads mean worse scores.
Further reading
- web.dev: Render-blocking resources — Google's guide to eliminating render blockers
- MDN: script async and defer — understand the difference between async and defer
- Next.js Script component — built-in script optimization strategies