Run a Google PageSpeed Insights test on any WordPress site and render-blocking resources almost always appear in the results. The fix sounds technical but the implementation is straightforward once you understand what is happening: certain CSS and JavaScript files force the browser to pause rendering your page while it downloads and parses them. Everything after them on the page does not render until they finish. For a broader look at WordPress performance improvements, visit our performance hub.
Performance plugins like WP Rocket and Perfmatters handle this automatically. But the underlying technique is just a few WordPress filters and a disciplined approach to how you enqueue scripts and styles. This guide walks through each technique with working PHP code that you can drop into your theme’s functions.php or a mu-plugin. For more code customizations like these, see our snippets collection.
When a browser parses HTML, it reads tags in order. When it encounters a <link rel="stylesheet"> tag or a <script> tag without async or defer, it stops rendering the page, downloads the resource, parses it, and only then continues. This is render blocking.
CSS is render-blocking by design – the browser cannot display styled content without the stylesheet. JavaScript is render-blocking by default – the parser stops because scripts can modify the DOM in ways that would invalidate already-rendered content. The browser’s caution is reasonable but the default behavior is too conservative for most scripts on a typical WordPress site.
| Resource Type | Default Behavior | Optimization |
|---|---|---|
| CSS in <head> | Render-blocking | Inline critical CSS, async-load full stylesheet |
| JS without attributes | Parser-blocking | Add defer or async attribute |
| JS with defer | Downloads in parallel, executes after HTML parse | Use for most site scripts |
| JS with async | Downloads in parallel, executes immediately when ready | Use for independent scripts (analytics) |
| CSS with media query | Non-blocking for non-matching media | Use media=print trick for async CSS loading |
WordPress outputs script tags via wp_enqueue_script(). The script_loader_tag filter lets you modify the HTML of each script tag before output, allowing you to add async or defer attributes to specific scripts by handle name:
async vs defer – Which to Use When
Use async for scripts that do not depend on the DOM and do not depend on other scripts. Analytics tags, tracking pixels, and chat widgets are good candidates. They download in parallel with HTML parsing and execute immediately when downloaded, potentially before the DOM is ready – which is fine for these scripts since they do not manipulate the page content.
Use defer for scripts that need the DOM to be ready before running but are not critical for initial page rendering. Most theme scripts, custom JavaScript, non-critical plugins, and any script that manipulates existing DOM elements should use defer. Deferred scripts download in parallel with HTML parsing but execute after the HTML is fully parsed and before DOMContentLoaded fires.
Never defer jQuery. Countless WordPress plugins call jQuery() inline or on document ready. If jQuery is deferred, those calls fail because jQuery is not available when the inline scripts execute. The same applies to any script that other scripts depend on – check your dependency tree before adding defer to anything.
CSS is the harder problem. You cannot simply defer a stylesheet – the page will flash unstyled content until it loads. The solution is to split your CSS into two parts: critical CSS (the minimum styles needed to render above-the-fold content) which you inline directly in the <head>, and the full stylesheet which loads asynchronously without blocking render.
The media="print" onload="this.media='all'" trick is the most reliable cross-browser method for loading CSS asynchronously. Setting media to print makes the browser download the stylesheet without blocking render (it considers it non-critical for screen rendering). The onload handler switches it to media="all" once downloaded, applying it to the page. The <noscript> fallback ensures CSS loads normally for the rare visitor with JavaScript disabled.
Generating critical CSS is the harder part. Tools that help: Critical (an npm package by Addy Osmani), Penthouse, or the online tool criticalcss.com. The output is a CSS file containing only the rules needed for above-the-fold rendering on the page types you specify. For a content site, you typically generate critical CSS for the homepage, single post, and category page templates separately.
The most effective way to eliminate render-blocking resources is to not load them on pages that do not need them. WordPress makes this straightforward via conditional tags in wp_enqueue_scripts:
This is the highest-impact optimization available. Removing WooCommerce scripts from non-shop pages, contact form scripts from pages without forms, and slider scripts from pages without sliders can each shave hundreds of kilobytes from your page weight. The dequeue approach shown here works with any plugin – you do not need to edit the plugin itself, just dequeue its handles from your theme or a mu-plugin.
To apply these optimizations, you need to know the handle names WordPress uses for each script and stylesheet. Three ways to find them:
- View page source: Look for
<script id="handle-name-js"– WordPress adds the handle as an ID attribute to script tags. Similarly,<link id="handle-name-css"for stylesheets. - Query Monitor plugin: The Scripts and Styles panels list every enqueued asset on the current page with its handle, URL, dependencies, and location (header/footer).
- Check the plugin’s code: Any well-written plugin calls
wp_register_script('my-handle'...)orwp_enqueue_script('my-handle'...)– the first argument is the handle name you use in dequeue/filter calls.
Scripts in the <head> block rendering until they download and execute. Scripts loaded in the footer (before </body>) do not block initial page rendering because the page content is already parsed by the time the browser reaches them. WordPress places scripts in the footer when you pass true as the fourth parameter to wp_enqueue_script().
For existing plugin scripts you do not control, the wp_scripts filter lets you move them to the footer by changing their group assignment. Group 0 is the header, group 1 is the footer. Combining footer loading with defer attributes on non-critical scripts is the most aggressive optimization approach – just test thoroughly, as some scripts break when moved to the footer because they have dependencies that load in the header.
After implementing these optimizations, test in order of importance:
- Visual check: Browse every page type on your site. Look for flash of unstyled content, layout shifts, broken interactions, and JavaScript errors in the browser console.
- PageSpeed Insights: Run the specific page through Google PageSpeed Insights. Render-blocking resources should be reduced or eliminated. Check both mobile and desktop scores.
- Chrome DevTools: Use the Performance panel to record a page load and look at the waterfall. Resources that previously blocked rendering should now show as parallel downloads.
- WebPageTest: Run a test with a real browser from a geographic location similar to your target audience. The filmstrip view shows exactly when content becomes visible during the page load.
Resource hints tell the browser about resources it will need soon, giving it a head start on downloading them. The rel="preload" link tag is particularly useful for above-the-fold resources: fonts, hero images, and critical CSS that you know will be needed immediately on page load.
Preload hints are output via the wp_head action in WordPress. Add them for resources that appear above the fold on high-traffic page templates. For example, if your header always shows the same logo image, preloading it eliminates the render delay caused by the browser discovering it only after parsing the HTML. The browser starts downloading it before it encounters the img tag in the page body.
Font preloading deserves special mention. Custom web fonts are a common source of layout instability (Cumulative Layout Shift in Core Web Vitals) because the browser reserves space for text but does not know the font metrics until the font file downloads. Preloading fonts reduces this window. Combine preload with font-display: swap in your CSS to show system fonts immediately and swap in the custom font when ready – this eliminates invisible text during font loading completely.
Third-party scripts – Google Analytics, Hotjar, Facebook Pixel, Intercom, chat widgets – are often the worst render-blocking offenders because you have no control over how fast their servers respond. A slow third-party CDN blocks your page just as effectively as a slow resource on your own server.
- Use async for analytics and tracking: These scripts do not need the DOM and should never block rendering. All major analytics platforms support async loading and document it explicitly.
- Delay non-critical scripts until after user interaction: Chat widgets, feedback tools, and marketing scripts can wait until the user scrolls or clicks. This is what WP Rocket calls Delay JavaScript Execution – you can implement it manually by wrapping script loading in a scroll or click event listener.
- Self-host Google Fonts: Google Fonts loads from external servers with connection overhead including DNS lookup, TCP handshake, and TLS negotiation. Download the font files and serve them from your own domain to eliminate this overhead entirely. The Google Webfonts Helper tool generates the CSS and downloads the font files in all needed formats.
- Use resource hints for third-party origins: Add dns-prefetch and preconnect hints for domains you will load resources from. This pre-establishes the connection before the browser discovers it needs the resource, cutting the time to first byte for third-party resources significantly.
WooCommerce is one of the worst offenders for loading scripts and styles on every page of your site regardless of whether that page has any shop functionality. By default, WooCommerce loads its cart fragments script (which powers the mini-cart widget), its main JavaScript file, and multiple stylesheets on every single page, including blog posts, the about page, and the contact page that have nothing to do with your shop.
The cart fragments script is particularly expensive because it makes an AJAX request on every page load to check whether the cart contents have changed. On a site where 80% of visitors never visit the shop, this means 80% of your AJAX requests for cart fragments are wasted. Disable cart fragments on non-shop pages, or replace the AJAX cart check with a lighter alternative that only runs when the visitor has items in their cart.
WooCommerce stylesheets can be conditionally loaded the same way as other plugin assets. The main handles to dequeue on non-shop pages are woocommerce-general, woocommerce-layout, and woocommerce-smallscreen for CSS, and wc-cart-fragments, wc-add-to-cart, and woocommerce for JavaScript. Keep these loaded on shop pages, product pages, cart, checkout, and my-account pages where they are genuinely needed, and dequeue them everywhere else.
For WooCommerce stores where the mini-cart widget appears in the header on every page, you still need cart fragments on all pages to keep the cart count updated. In this case, consider lazy-loading the mini-cart, load a static cart icon initially and fetch the cart contents via AJAX only when the visitor hovers over or clicks the cart icon. This eliminates the automatic AJAX request on every page load while keeping the cart functionality available on demand. Several lightweight plugins implement this pattern, or you can build it with a small custom script that replaces the default WooCommerce cart fragments behavior.
Several mistakes are common when first implementing render-blocking optimizations in WordPress:
- Deferring jQuery or jQuery-dependent scripts: Most WordPress themes and plugins assume jQuery is available synchronously. Deferring jQuery breaks every plugin that calls jQuery() in an inline script. Defer only scripts that explicitly handle deferred loading or that you have verified have no synchronous dependencies.
- Over-aggressive critical CSS: Including too much CSS in the inlined critical block defeats the purpose. Keep it to genuinely above-the-fold content – header, navigation, hero section. Full-page CSS inlined in the head is larger than just loading the stylesheet normally.
- Not testing on mobile: Mobile browsers handle async and defer differently in edge cases. Always test render-blocking changes on actual mobile devices or accurate mobile emulation, not just desktop DevTools.
- Breaking the noscript fallback: The async CSS loading trick requires a noscript fallback. Without it, users with JavaScript disabled never load your stylesheets.
- Forgetting the Gutenberg editor: Some optimizations that dequeue scripts on the frontend also affect the block editor. Test your admin post editing after applying dequeue changes.
- Ignoring third-party scripts: Google Analytics, Facebook Pixel, chat widgets, and advertising scripts are often loaded directly in the theme header via inline script tags rather than through wp_enqueue_script. These bypass your WordPress filters entirely. Move them to a proper enqueue function or use the Google Tag Manager container approach where a single async script loads everything else after the initial page render completes.
- Applying optimizations without a staging test: Render-blocking changes can break visual layouts in subtle ways, fonts flash unstyled, hero images shift position, carousels fail to initialize. Always test on a staging environment first and check multiple browsers including Safari, which handles async CSS differently than Chrome and Firefox in some edge cases.
Google’s Core Web Vitals metrics directly measure the user experience problems that render-blocking resources cause. Largest Contentful Paint (LCP) measures how long until the largest visible element on the page loads – render-blocking CSS and JS delay this by preventing the browser from rendering anything while they are being processed. First Contentful Paint (FCP) is the time until any content appears – render-blocking resources directly extend this metric.
Eliminating render-blocking resources typically improves LCP by 0.5-1.5 seconds on typical WordPress sites. The exact improvement depends on how many render-blocking resources your site loads and how large they are, a site loading 15 plugin stylesheets in the head will see more dramatic improvement than a minimal site with 3 stylesheets. Combined with properly sized images, font optimization, and server-side performance tuning including object caching, GZIP or Brotli compression, and HTTP/2 multiplexing, render-blocking elimination consistently moves sites into the green ranges for Core Web Vitals on desktop and significantly improves mobile scores where the impact is even more pronounced due to slower network connections and less powerful processors.
The order of implementation matters for maximum Core Web Vitals impact: start with the biggest wins first. For most WordPress sites, the order is: conditional loading first (remove assets not needed on specific pages), then defer and async for remaining scripts, then critical CSS inlining. Each step has diminishing returns, but the first step often provides the largest improvement since it completely eliminates entire resources rather than just changing when they load.
WordPress 6.3 introduced the wp_script_attributes filter and WordPress 6.4 added the strategy parameter to wp_register_script and wp_enqueue_script. This native approach lets you specify loading strategy directly when registering scripts, eliminating the need for the script_loader_tag filter for new scripts you control.
The strategy parameter accepts two values: defer and async. When you register a script with a strategy, WordPress handles the attribute addition automatically and also manages dependency chains intelligently. If script A depends on script B, and script A is registered with strategy defer, WordPress ensures both scripts are deferred and execute in the correct dependency order. This is a significant improvement over manually adding defer via script_loader_tag, where you had to manually track dependency chains to avoid breaking scripts that depend on each other.
For themes and plugins targeting WordPress 6.4 and newer, use the native strategy parameter instead of the filter approach. For backward compatibility with older WordPress versions, the script_loader_tag filter remains the reliable approach. If you need to support both old and new WordPress versions, check the WordPress version and use the appropriate method conditionally.
The strategy parameter is particularly valuable for plugin developers distributing code across many sites. Rather than relying on the theme or a performance plugin to add defer to your scripts, you can declare the correct loading strategy as part of your script registration. This means your plugin performs well out of the box without requiring the user to configure a performance plugin or add custom filter code.
Before implementing render-blocking optimizations, establish a performance baseline so you can measure the actual impact of your changes. Without baseline measurements, you are guessing whether your optimizations helped, and you cannot identify regressions if a future plugin update undoes your work.
Run Google PageSpeed Insights on your five most important page types: homepage, a high-traffic blog post, a category archive, a product page if you run WooCommerce, and your contact or lead generation page. Record the LCP, FCP, and Total Blocking Time (TBT) scores for both mobile and desktop. Save these results, a screenshot or a spreadsheet entry, before making any changes.
After implementing each optimization technique (defer/async scripts, critical CSS inlining, conditional loading), re-run PageSpeed Insights on the same pages. Compare the before and after numbers. The typical impact pattern is that conditional loading shows the largest improvement because it eliminates entire resources, defer/async shows moderate improvement by changing when resources load, and critical CSS inlining shows the most improvement on First Contentful Paint specifically.
For ongoing monitoring, set up a free CrUX (Chrome User Experience Report) dashboard that tracks your Core Web Vitals from real user data over time. This catches performance regressions from plugin updates, theme changes, or new third-party scripts that your development team adds without considering their render-blocking impact. WebPageTest also offers a free API that you can use to schedule regular performance tests and alert on regressions.
Document which scripts you deferred, which stylesheets you made async, and which assets you conditionally loaded. When something breaks after a WordPress or plugin update, this documentation helps you quickly identify whether one of your optimizations conflicts with the update. Keep this documentation in a comment at the top of your optimization mu-plugin file, it stays with the code and is visible to any developer who works on the site after you.
Removing render-blocking resources in WordPress comes down to three techniques used in combination: add async or defer to non-critical scripts via the script_loader_tag filter, inline critical CSS and load full stylesheets asynchronously using the media trick, and conditionally load plugin assets only on the pages that need them. Each technique requires knowing your asset handles, testing after each change, and maintaining the code as your plugin set evolves.
For related server-side WordPress performance improvements, the guide on controlling the WordPress Heartbeat API covers reducing unnecessary admin-ajax.php calls. For secure server configuration that supports these optimizations, see our guide on protecting WordPress from malware and crypto miners which covers .htaccess and security header configuration.
Critical CSS Performance Optimization Render Blocking Script Optimization WordPress Speed
Last modified: March 26, 2026