Page Speed Optimization

Eliminate render-blocking resources: how to load CSS and JavaScript safely

Remove unnecessary dependencies from the first render while keeping essential styles and interactions reliable.

Two resource bars with completion marks connect to a rendered page outline

Reduce render-blocking delays by identifying the CSS and JavaScript that the browser must process before useful content can appear, then moving unnecessary work out of that path. Keep essential styling available, use appropriate script loading modes and avoid turning every resource into a preload. The goal is an earlier correct render, not merely removing a warning while producing an unstyled or broken page.

Separate rendering, parsing and main-thread work

The browser parses HTML into the DOM and styles into information used to calculate presentation. It combines the relevant structure and styles, computes layout and paints pixels. A stylesheet that applies to the current page can hold up rendering because the browser needs its rules to determine how content should look.

Classic scripts without async or defer can pause HTML parsing while they are fetched and executed. That parser blocking is distinct from an explicit render-blocking instruction, although it can delay content discovery and first rendering. A script can also wait for an earlier blocking stylesheet because it may inspect styles. The resulting dependency chain matters more than the file extension alone.

Even a script that downloads asynchronously still executes on the main thread unless its work is explicitly moved elsewhere. Long execution can delay rendering and input processing after the download is complete. A loading attribute changes scheduling; it does not eliminate the work in the file.

Start by locating the affected user-visible event. If an image appears late, distinguish its download from the CSS or JavaScript that prevents it being displayed. The LCP guide helps split those stages. If the page paints promptly but becomes unresponsive, use JavaScript execution analysis rather than treating the problem only as initial render blocking.

Read the dependency chain in current tools

The older Lighthouse label “Eliminate render-blocking resources” still appears in search queries and old reports. Lighthouse 13 moved many legacy performance audits into insights; current reports may use a render-blocking requests insight instead. The principle remains the same, but do not expect every old audit identifier or report location to exist in a newer JSON file.

Record a navigation in the Performance panel and inspect the Network waterfall. Identify the document, stylesheets, scripts, fonts and LCP resource. Look for requests discovered only after another file finishes, and inspect their initiators. A small stylesheet found through an @import chain can start later than a larger stylesheet linked directly in HTML.

Keep a baseline screenshot or filmstrip to show what is visible before and after the proposed change. A faster first paint that contains broken navigation or unstyled content may not be a useful improvement. Inspect the full page as well, because code removed from the initial view can still support content lower down.

Do not add the estimated savings from every insight as though they were independent measurements. Two suggestions can refer to the same dependency chain. Test one coherent change and examine actual request timing and rendering afterward.

Keep critical CSS small and maintainable

Critical CSS is the styling required for the initial visible layout. Inlining a carefully selected set can avoid a separate request for those rules. Inlining the entire site's stylesheet is different: it enlarges every HTML response and prevents that CSS from being reused as a separate cached file across navigations.

The initial viewport depends on device, language, consent state and content. A critical set extracted from one desktop screenshot may omit mobile navigation, a long translated heading or an error message. Treat extraction as a build process with representative templates and visual checks, not a one-time copy from a single page.

This small example keeps essential layout in the document and loads a print-only stylesheet through a media condition.

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Performance notes</title>
  <style>
    body { margin: 0; font-family: system-ui, sans-serif; line-height: 1.6; }
    header, main { max-width: 70rem; margin-inline: auto; padding: 1rem; }
    img { max-width: 100%; height: auto; }
  </style>
  <link rel="stylesheet" href="/styles/print.css" media="print">
</head>
<body>
  <header>Performance notes</header>
  <main><h1>A readable first render</h1><p>Essential content is available immediately.</p></main>
</body>
</html>

A nonmatching media condition means the stylesheet does not block rendering for the current medium, although the browser can still download it. Do not set a screen stylesheet to media="print" and leave it there; that changes where its rules apply. Hacks that switch media after loading require careful handling of script failure, Content Security Policy and unstyled content.

If you inline styles, ensure the site's Content Security Policy permits them through the intended nonce or hash mechanism. Do not weaken an established policy simply to copy an optimization example. Your build needs to keep the inline critical rules and the full stylesheet synchronized so stale declarations do not create a later layout jump.

Split CSS by actual page needs

A shared base stylesheet plus route- or component-specific styles can prevent a simple article from downloading the complete styling for a complex application. Keep the shared base modest and load component styles where the component is actually used. Too many tiny files can create scheduling and maintenance overhead, so choose boundaries around meaningful page features.

Remove unnecessary @import chains when you control the build. A stylesheet discovered through another stylesheet adds a dependency before it can be fetched. Linking known essential styles directly in HTML, or resolving imports at build time, makes discovery more explicit. This is about request dependencies, not a claim that every imported rule is inherently slow.

Be cautious with runtime CSS-in-JS. Some implementations need JavaScript to download and execute before they insert styles. That can place CSS discovery behind script work and require additional style recalculation. Server extraction or build-time CSS can change that path, but the correct solution depends on the library, framework and version.

Inspect the generated response rather than judging by the component source. A framework may already extract CSS, while another configuration may inject it at runtime. Confirm whether the initial HTML includes usable styles before removing or replacing a library.

Choose async, defer and modules by dependency

Script loading should follow the script's role. An independent script can tolerate different ordering; application code that depends on the DOM or a library may not. Adding async to everything can turn a performance change into a race condition.

The common external-script modes have distinct behavior.

Script form Fetching Execution and ordering
Classic script without attributes Can pause the parser Executes before parsing continues
Classic script with defer Fetches while parsing continues Runs after parsing, in document order, before DOMContentLoaded
Classic script with async Fetches while parsing continues Runs when ready, without document-order guarantees
type="module" without async Fetches the module graph while parsing continues Deferred by default; dependencies are part of the graph

For classic external scripts that depend on one another, preserve their order with defer.

html
<script src="/scripts/vendor.js" defer></script>
<script src="/scripts/navigation.js" defer></script>

For a module entry point, declare its type and let module imports express dependencies.

html
<script type="module" src="/scripts/app.js"></script>

For a truly independent script that does not need a particular position in the execution order, async can be appropriate.

html
<script src="/scripts/independent-widget.js" async></script>

These examples are alternatives for different resources, not instructions to include three implementations of the same feature. defer has no useful effect on an ordinary inline classic script. Module scripts are already deferred by default, while adding async to a module changes when its graph executes. Check the script element documentation before changing a mixed legacy page.

After changing script attributes, test menus, search, validation, checkout and consent behavior as applicable. Inspect errors in the Console. A library loaded with defer can still be referenced too early by a later inline classic script. Fix the dependency rather than repeatedly changing attributes until one reload happens to work.

Use preload and preconnect for specific missing time

Preload tells the browser about a resource needed for the current page before it would otherwise be discovered. It does not apply a stylesheet, execute a script or render an image by itself. The resource still needs its normal consumer, and the preload must match the eventual request.

This example brings forward one font that the page's CSS will use immediately.

html
<link rel="preload" href="/fonts/site-regular.woff2"
      as="font" type="font/woff2" crossorigin>

Use the correct as value and request mode. Incorrect hints can produce a second fetch or an unused download. Do not preload all CSS, scripts and images: prioritizing everything can make the truly critical requests compete with work that was previously deferred.

Preconnect addresses connection setup to an origin, not transfer size or execution time. It can be useful for a critical cross-origin resource that the browser would otherwise discover late. Limit it to origins that are actually needed early, and use the appropriate CORS setting for the eventual resource. A same-origin connection already used for the document generally does not need a new hint merely because more files share that origin.

For module dependencies, modulepreload has semantics designed for module fetching and preparation. Use framework-generated hints where available and inspect what they emit. Do not replace them mechanically with generic script preloads without understanding the module graph and request mode.

Use Coverage as a test record, not a deletion command

Chrome DevTools Coverage shows used and unused portions of CSS and JavaScript during the recorded page activity. Open the panel, start coverage with a reload, then exercise the page. Expand navigation, open dialogs, scroll, change tabs and test responsive layouts that load different components.

Code marked unused was unused in that recording. It may be required by another route, a later interaction, an error state or a different language. A stylesheet's mobile rules can look unnecessary when you inspect only a desktop viewport. Do not delete every red region from a single session.

Map large unused bundles back to their source modules, templates or plugins. Remove a feature that is no longer needed, load a route-specific dependency only on that route or configure the build to eliminate truly unreachable code. This produces a maintainable change instead of hand-editing generated bundles that the next build will overwrite.

Retest after removing code and record the interactions included. Coverage is strongest when combined with a dependency graph and visual or functional checks. It does not prove that a dynamic application no longer needs a particular function in every possible state.

Check CMS and plugin transformations together

CMS sites often accumulate overlapping optimization settings. A theme, caching layer and performance plugin may each combine styles, delay scripts or rewrite resource hints. The resulting HTML can differ substantially from the settings screen, so inspect the delivered response and request initiators.

Do not enable every delay option at once. Preserve an understandable baseline, change one layer and verify the critical flows. Some scripts must run before a dependent inline initialization; some styles support a banner or mobile menu that is absent in a simple desktop test.

When a cache stores generated HTML or CSS, invalidate the relevant layer after changing templates and verify both fresh and cached responses. Otherwise, apparently inconsistent behavior may be two versions of the page rather than a browser scheduling mystery. Keep a rollback path for the configuration being changed.

Verify an earlier correct render

Repeat the same navigation under consistent conditions and compare the first useful content, LCP, layout movement and main-thread work. Check the full page and important interactions. A reduction in blocking requests is useful evidence only when the visible experience and functionality remain sound.

Include a narrow viewport, a longer translated heading and a visit with an empty cache. If font discovery is still delaying text, continue with web font optimization. Save the dependencies you changed in the performance checklist and use the page speed optimization guides for the next bottleneck shown by the trace. Keep only changes whose benefit survives a repeatable test.

Related guides