When the browser encounters a <script> tag or a <link rel="stylesheet"> in your HTML, it stops rendering the page until those files are downloaded and processed. On a WordPress site with 10+ plugins each loading their own CSS and JS, this can add 1–3 seconds to your First Contentful Paint (FCP).

Why render-blocking resources happen in WordPress

WordPress and its plugins load assets via wp_enqueue_scripts(). Without configuration, these are placed in the <head> section or just before </body>. Head scripts block rendering entirely. Even footer scripts block the main thread and increase Total Blocking Time (TBT).

The most common offenders on WordPress sites:

Step 1: Identify your render-blocking resources

Run your site through PageSpeed Insights (or use WPStats which fetches PSI data for you). Look for the "Eliminate render-blocking resources" audit. It lists every blocking file with its potential savings in milliseconds.

Also open Chrome DevTools (F12) → Network tab → reload the page → sort by Waterfall column. Files with a long grey bar before the colored bar are blocking the render.

Step 2: Defer JavaScript (most impactful)

Adding the defer attribute to script tags tells the browser to download the script in parallel but only execute it after the HTML has been parsed — eliminating the render block entirely.

Option A: WP Rocket (easiest)

WP Rocket → File Optimization → JavaScript → enable "Load JavaScript deferred" and "Delay JavaScript execution." This automatically adds defer to all scripts and delays non-critical JS until user interaction.

Option B: functions.php (manual)

add_filter('script_loader_tag', function($tag, $handle, $src) {
    // Exclude critical scripts that must run synchronously
    $exclude = ['jquery-core', 'jquery-migrate'];
    if (in_array($handle, $exclude)) return $tag;

    // Add defer to all other scripts
    return str_replace(' src', ' defer src', $tag);
}, 10, 3);

Warning: Deferring scripts can break functionality if a script depends on another running first. Always test thoroughly after enabling defer. Start with non-essential scripts.

Option C: Perfmatters plugin

Perfmatters provides granular per-script/per-page control — defer this script on all pages, disable that script on every page except WooCommerce. The most surgical approach.

Step 3: Optimize CSS delivery

CSS is always render-blocking — the browser won't paint anything until all CSS is loaded. The goal is to minimize critical CSS and defer non-critical stylesheets.

Inline critical CSS

Extract the CSS needed for above-the-fold content and inline it directly in <head>. Load the full stylesheet asynchronously:

<!-- Inline critical CSS -->
<style>
  /* Only styles needed for above-the-fold content */
  body { margin: 0; font-family: sans-serif; }
  .header { background: #fff; height: 60px; }
  .hero { min-height: 80vh; }
</style>

<!-- Load full CSS asynchronously -->
<link rel="preload" href="style.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="style.css"></noscript>

Generating critical CSS manually is tedious. Use the Critical CSS plugin or WP Rocket's "Remove Unused CSS" feature which automates this process.

Minify and combine CSS

Fewer CSS files mean fewer HTTP requests (less important with HTTP/2) and less total bytes. Enable CSS minification in your caching plugin. Be cautious with combination — it can break some theme/plugin combos.

Step 4: Optimize Google Fonts

Google Fonts loaded the traditional way are render-blocking. Three ways to fix them:

Option A: Add display=swap

<link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">

This allows text to render immediately with a fallback font, replacing it with the custom font when loaded.

Option B: Preconnect + preload

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">

Option C: Self-host fonts (best performance)

Download your fonts using the Google Fonts Helper tool, upload to your server, and serve them directly. Eliminates the external DNS lookup entirely and gives you full cache control.

Step 5: Disable scripts on pages where they're not needed

Contact Form 7 loads its scripts on every page — even pages with no contact form. Slider scripts load everywhere even without a slider. This is unnecessary overhead.

Use Perfmatters or Asset CleanUp to disable specific plugin scripts on specific page types. Common wins:

// Disable CF7 scripts globally, re-enable only on contact page
add_action('wp_enqueue_scripts', function() {
    if (!is_page('contact')) {
        wp_dequeue_style('contact-form-7');
        wp_dequeue_script('contact-form-7');
    }
}, 99);

Step 6: Use resource hints

Resource hints tell the browser to prepare for resources it will need soon:

<!-- DNS prefetch for external domains -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//www.google-analytics.com">

<!-- Preconnect for high-priority external resources -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- Preload critical resources -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/wp-content/themes/theme/style.css" as="style">

Measuring the improvement

After making changes, re-run PageSpeed Insights (or WPStats) and look for improvements in:

Quick win: The WordPress emoji detection script is loaded in <head> and makes an extra DNS lookup. Remove it with: remove_action('wp_head', 'print_emoji_detection_script', 7); in functions.php. Instant FCP improvement for most sites.

See your WordPress FCP and TBT scores now

WPStats pulls real PageSpeed Insights data — see exactly how render-blocking resources are affecting your scores.

Check my PageSpeed scores