# Fix Render-Blocking CSS and JavaScript in 2026: WordPress Asks for Defer on Two of Its Own 190 Scripts

> WordPress gives scripts a dependency-aware defer API and stylesheets nothing. Read the attribute core leaves when it refuses your deferral, and fix the dependent.

- Published: 2026-10-07
- Author: xSpeed Cache Team
- Tags: Render Blocking, Performance, Tutorial, Core Web Vitals, WordPress, check:A1, fix:render-blocking
- Canonical: https://xspeedcache.com/blog/fix-render-blocking-resources-wordpress/

---

Updated October 2026

WordPress 7.1.2 registers 190 script handles in `script-loader.php` and gives exactly two of them a loading strategy, counted from the core source on 7 October 2026. The same morning our own company homepage, wpdeveloper.com, failed check A1 with eight render-blocking files and roughly 6.2 seconds of attributed first paint, in a speed report we ran that day.

The fix most guides offer is a list of switches. The more useful question is which half of this problem WordPress protects you on, because it protects you on exactly one. Scripts have a dependency-aware API that refuses unsafe deferrals without saying so. Stylesheets have nothing. This guide covers finding every blocking file, reading the receipt core leaves when it overrules you, and what order to work in.

## Quick Summary: Which Half of the Problem Is Yours

| If you want to… | Do this | Why |
|:---|:---|:---|
| Know what actually blocks | Count the files from your own HTML | The report names three and stops |
| Make scripts non-blocking safely | Set a `strategy`, then read the receipt | Core refuses unsafe deferrals itself |
| Find why a defer did nothing | Look for `data-wp-strategy` with no `defer` | That attribute is the only trace of a refusal |
| Make stylesheets non-blocking | Pair async CSS with critical CSS | Nothing in core checks this half |
| Verify any of it | Run the free scan, then read the head | Check A1 sizes it in milliseconds |

## What Blocks First Paint: Chrome's Two Rules, and the Three Files You Get Told About

[Chrome's Lighthouse documentation](https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources) states the test outright, and it is narrower than most advice implies.

| Resource | Render-blocking when | Not blocking when |
|:---|:---|:---|
| `<script src>` | In `<head>` with neither `defer` nor `async` | It carries either attribute, or sits outside `<head>` |
| `<link rel="stylesheet">` | No `disabled` attribute and no media query matching the device | A media attribute scopes it off this device |

That second row catches people. Per Chrome's audit documentation, `media="all"` is considered render-blocking, and `wp_enqueue_style()` defaults its fifth parameter to `all`. Every stylesheet WordPress enqueues without an explicit media value is blocking by that definition.

The audit also moved. Chrome's documentation notes that the Eliminate render-blocking resources audit "has moved into the Render-blocking requests insight as of Lighthouse 13", and PageSpeed Insights served Lighthouse 13.5.0 on 7 October 2026. That is why the old heading is missing today.

The list you get is truncated. Our A1 line for wpdeveloper.com reads `8 files: frontend.min.css, slick-theme.css, betterdocs-el-edit.css…`, naming three of eight. Count them yourself:

```bash
curl -s -A 'Mozilla/5.0' https://example.com/ \
  | sed -n '/<head/,/<\/head>/p' \
  | grep -oE '<script[^>]+src=[^>]*>|<link[^>]+stylesheet[^>]*>'
```

Scripts carrying `defer` or `async` are fine, as are stylesheets with a real media query.

## Core Asks for Defer on Two of Its Own 190 Scripts, and Withdraws It Silently

Since 6.3, `wp_enqueue_script()` accepts a `strategy` of `defer` or `async` in its `$args` array, and `wp_script_add_data( $handle, 'strategy', 'defer' )` sets it on someone else's script. Core applies it to `comment-reply` (async) and `wp-embed` (defer), and to nothing else among the 190 handles in `script-loader.php`.

Asking is not getting. `WP_Scripts::get_eligible_loading_strategy()` in [`class-wp-scripts.php`](https://core.svn.wordpress.org/tags/7.1.2/wp-includes/class-wp-scripts.php) walks every dependent before writing the tag, and `filter_eligible_strategies()` returns an empty list, meaning blocking, in two cases the source states:

| Condition in the source | What it means for you |
|:---|:---|
| "For non-alias handles, an empty intended strategy filters all strategies" | One enqueued dependent with no strategy puts your script back in the critical path |
| "Handles with inline scripts attached in the 'after' position cannot be delayed" | A single `wp_add_inline_script( $handle, $code, 'after' )` cancels the deferral |

Both tests recurse, so a dependent four links down is enough. No admin notice, no debug warning and no Site Health test fires, and the method holding the reason is private.

## The Receipt: `data-wp-strategy` With No `defer`

Core leaves one trace. When the intended strategy was delayed it writes `data-wp-strategy` onto the tag whatever happens, and adds the real `defer` or `async` only if the deferral survived:

```html
<script src="…" defer data-wp-strategy="defer" id="example-js"></script>  <!-- asked, granted -->
<script src="…" data-wp-strategy="defer" id="example-js"></script>        <!-- asked, refused -->
```

That makes the diagnosis one command:

```bash
curl -s -A 'Mozilla/5.0' https://example.com/ \
  | grep -oE '<script[^>]*data-wp-strategy[^>]*>' | grep -v ' defer\| async'
```

Ten WordPress sites fetched on 7 October 2026 make the point. Seven carried the attribute, and on yoast.com three WooCommerce scripts, `jquery-blockui`, `selectWoo` and `wc-country-select`, carried `data-wp-strategy="defer"` and no `defer`. WooCommerce asked, core declined, on a site run by people who read this specification closely.

Once you have a refused handle, this names the dependent:

```bash
wp eval '$s=wp_scripts(); foreach($s->registered as $h=>$o){ if(in_array("wc-country-select",$o->deps,true))
  echo $h." → ".var_export($s->get_data($h,"strategy"),true)."\n"; }'
```

![Script tags with and without the granted defer attribute](https://xspeedcache.com/images/blog/render-blocking-defer-receipt.webp)

Any dependent printing `false` is a blocker. Give it a strategy, or exclude it.

One limit: the receipt exists only where a developer asked. A theme that never set a strategy leaves no trace, so an empty result proves nothing about your other scripts.

## Stylesheets Have No Strategy, So Nothing Will Catch Your Mistake

`wp_enqueue_style()` takes no strategy parameter, and `WP_Styles` carries no concept of deferral. The only levers core gives you are the `media` attribute and the `style_loader_tag` filter. No eligibility check, no refusal, no receipt.

![Core's script and stylesheet paths compared](https://xspeedcache.com/images/blog/render-blocking-two-halves.webp)

The pattern everyone reaches for exploits the media rule above. Google's guidance on [deferring non-critical CSS](https://web.dev/articles/defer-non-critical-css) documents the preload form; the print variant is what plugins ship:

```html
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
```

The browser fetches it at low priority because `print` does not match a screen, then promotes it on load. It causes a flash of unstyled content unless the above-the-fold rules are already inlined. Our [critical CSS documentation](https://xspeedcache.com/docs/critical-css/) agrees, which is why async CSS alone often looks worse than no change at all.

In xSpeed Cache, which is ours, the free **Load CSS Asynchronously** toggle on the **Minify** tab uses that print-then-all rewrite, and it also catches font stylesheets themes print into the page, bypassing WordPress's style queue. The settings are in [How to optimize CSS and JavaScript](https://xspeedcache.com/docs/css-and-javascript/), and [removing unused CSS](https://xspeedcache.com/blog/remove-unused-css-wordpress/) covers the byte job beside it.

The two safety models fail differently:

| Behaviour | Core's `strategy` | A plugin's defer toggle |
|:---|:---:|:---:|
| Decides per dependency chain | ✅ | ❌ |
| Refuses rather than break | ✅ | ❌ |
| Covers stylesheets | ❌ | ✅ |

Core is conservative and silent. A plugin is aggressive and needs an exclusion list, which is why xSpeed ships `jquery-core` and `jquery-migrate` excluded, and why [a broken menu after a delay setting](https://xspeedcache.com/blog/delay-javascript-broke-menu/) is a dependency problem.

## The Blocking Ledger: Four Questions Answered From Your Own HTML

Run these in order. Every answer comes from the page itself.

| # | Question | Where the answer is | What it tells you |
|:---|:---|:---|:---|
| 1 | How many files block? | The `<head>` extract above | The real count, not the three named |
| 2 | Which scripts were refused? | `data-wp-strategy` with no `defer` | Work you can finish without a setting |
| 3 | Which stylesheets block? | Media `all`, or absent | The half with no safety net |
| 4 | Which are yours to change? | The host in each URL | A third-party file is someone else's fix |

Its limits: this reads one URL, logged out, at one moment; a logged-in page enqueues a different set. Check A1's millisecond figure comes from a lab run rather than from visitors, so treat it as a size, not as what people felt. Question 2 sees only scripts whose developer asked.

## Checking It Worked, Starting With the Scan

The free [xSpeed Scan](https://xspeedcache.com/scan/) needs no account and grades any public URL. Read **check A1, "No render-blocking resources"**, in the assets dimension: it reports the failing file count and the attributed first-paint delay, which is the number that should fall. Check S5, First Contentful Paint, is where the gain shows as time.

If you would rather check by hand:

- Re-run the `<head>` extract from question 1 and compare counts.
- Open the page in a private window, network panel recording.

Change one setting at a time. [Minification and deferral break layouts in four distinct ways](https://xspeedcache.com/blog/layout-breaks-after-minification/), and a page that broke after four toggles cannot say which one did it.

Run the free scan on your own site: [xspeedcache.com/scan/](https://xspeedcache.com/scan/)

## Running This Across Twenty Sites You Did Not Build

On one site the ledger takes ten minutes. On an estate the first two questions are worth scripting, since the answer differs per theme.

[xSpeed Hub](https://xspeedcache.com/xspeed-hub/), our fleet dashboard, runs the scan across every connected site, so the worst ones surface without opening twenty dashboards. [WooCommerce stores](https://xspeedcache.com/use-cases/woocommerce/) are worth checking first: every refused handle found in the spot-check above belonged to WooCommerce.

The ceiling is also lower than the report implies: our [measured fix-priority study](https://xspeedcache.com/blog/wordpress-speed-fix-priority-order/) puts page weight ahead of the critical path on most sites.

## Common Mistakes People Make Deferring Scripts

- Turning on defer, seeing no change, and deciding the setting is broken. Core may have refused it.
- Enabling async CSS without critical CSS, then blaming the plugin for the unstyled flash.
- Testing while logged in, where a different set of scripts loads and the cache is bypassed.

## Frequently Asked Questions

### I set `strategy` to `defer` and the tag has no `defer` attribute. What did I do wrong?

Nothing. Core refused it because a dependent either has no strategy set or carries an `after` inline script. The attribute records that you asked.

### My defer toggle works on one page and not another. Why?

Different templates enqueue different scripts, so a dependent that blocks the chain on a product page may not load on the homepage. Run the ledger per template, not per site.

### I deferred everything, it looked fine to me, then customers reported a broken slider. How?

Above-the-fold interactive elements are the usual casualty, and they often work for you because your browser cached the script already. Test in a private window.

### Why does my report say nothing about render-blocking resources any more?

Chrome moved it into the Render-blocking requests insight in Lighthouse 13. The finding is there under a different heading.

### Is `async` better than `defer`?

For most WordPress scripts, no. `async` runs whenever the file arrives, which breaks anything order-dependent. Core grants it only when every dependent tolerates it.

### I added `media="print"` to a stylesheet and the page rendered unstyled for a second. Expected?

Yes. The sheet arrives after first paint now, so inline the above-the-fold rules as critical CSS.

### Which plugins handle this?

Several do the job honestly. xSpeed Cache is ours and covers defer, delay and async CSS free, with critical CSS in Pro; WP Rocket and LiteSpeed Cache ship comparable deferral; Autoptimize is a free option for asset delivery alone. Compare them on [Free vs Pro](https://xspeedcache.com/free-vs-pro/) or the full [capability comparison](https://xspeedcache.com/comparison/).

### Can I defer a script a third-party plugin registered?

Yes, with `wp_script_add_data( 'their-handle', 'strategy', 'defer' )` on a late hook. Core still applies its eligibility test and may decline. A plugin-side rewrite ignores that test, which is why xSpeed's **Defer / Delay Exclusions** field takes handles or URL fragments.

### Does any of this move my Largest Contentful Paint?

It moves first paint directly, and LCP only when the largest element waited on a blocked stylesheet. If your LCP is an oversized image, fix the bytes.

### I have one blocking file left and it is on a CDN I do not control. Now what?

Question 4 exists for this. A third-party sheet you cannot edit can often be self-hosted instead, which is usually the larger win anyway.

## Conclusion: Read the Receipt Before You Touch a Setting

Render-blocking on WordPress is two jobs. On scripts, core already made the safe decision and recorded when it overruled you, so the cheapest work is reading `data-wp-strategy` and fixing one dependent. On stylesheets there is no protection, which makes critical CSS a prerequisite.

| Your goal | Start here | Then |
|:---|:---|:---|
| Fastest honest win | The `data-wp-strategy` grep | Fix the dependent, not the setting |
| Biggest first-paint move | Async CSS with critical CSS | Verify in a private window |

**What to do this week:**

1. Run the free scan on your slowest template and note check A1's file count and milliseconds.
2. Extract your own `<head>` and count what blocks.
3. Grep for `data-wp-strategy` without `defer`, and fix the dependent behind each hit.
4. For all of it in one panel, xSpeed Cache is ours and free on wordpress.org, with critical CSS on Pro at $29 a year founding price, a lifetime licence option, and a 14-day money-back guarantee.

If the count does not budge, post your A1 line and `data-wp-strategy` output below and we will read the chain with you.
