One of the most common web performance questions in engineering interviews is:
What is a render-blocking resource? How do you optimize stylesheets and scripts to prevent them from blocking the first paint of a webpage?
When a browser encounters stylesheet link elements or script tag elements in the HTML document, it pauses layout painting until the assets are downloaded and parsed. These elements are called render-blocking resources.
1. How Render-Blocking Occurs#
During page load, the browser builds the DOM (Document Object Model) and the CSSOM (CSS Object Model). The page cannot paint until both trees are combined into the Render Tree:
HTML ──────► DOM ──────┐
├──────► Render Tree ──────► Paint UI
CSS ───────► CSSOM ────┘
- CSS blocks Paint: The browser delays rendering to prevent a flash of unstyled content (FOUC). Therefore, all stylesheets in the document head block rendering.
- JS blocks DOM construction: Standard script tags block the HTML parser entirely, because scripts can modify the DOM using
document.writeor access style attributes.
2. Optimizing Render-Blocking CSS#
To prevent CSS from delaying first paint times:
A. Inline Critical Path CSS#
Split your CSS into critical (needed for the above-the-fold content visible on load) and non-critical styles:
- Inline the critical CSS directly inside a
<style>block in the HTML head. - Load the remaining non-critical stylesheet asynchronously:
<link
rel="preload"
href="non-critical.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript>
<link rel="stylesheet" href="non-critical.css" />
</noscript>
B. Media Attributes for Stylesheets#
If a stylesheet is only needed for printing or specific screens, tell the browser to download it with low priority:
<link rel="stylesheet" href="print.css" media="print" />
The browser still downloads it, but it does not block the render tree on normal screens.
3. Optimizing Render-Blocking JS#
To prevent JavaScript from blocking HTML parsing:
- Use
deferorasyncattributes: Apply these to all external script elements (see script execution guides). - Code Coverage Profiling: Use the Chrome DevTools Coverage Tool to record code execution. Identify unused code chunks and split them out.
- Dynamic Imports (
React.lazy/import()): Load code blocks asynchronously only when user interactions occur (e.g. loading a modal script only when the modal open button is clicked).
// Dynamically import heavy chart library only when needed
async function showCharts() {
const { Chart } = await import('chart.js');
const chart = new Chart(...);
}
Key Takeaways#
- DOM/CSSOM sync: Rendering requires both the DOM and CSSOM to be fully ready; CSS is always render-blocking.
- Inline Critical Styles: Inlining critical CSS directly in the HTML document eliminates round-trip asset requests.
- De-prioritize Stylesheets: Set proper
mediaqueries on links to keep screen-specific CSS from blocking default viewport paints. - Defer Script Tags: Always ensure scripts are loaded using
deferorasyncto keep the HTML parser running smoothly.