Fix Render-Blocking CSS and JavaScript in 2026: WordPress Asks for Defer on Two of Its Own 190 Scripts
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 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:
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 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:
<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:
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:
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"; }'

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.

The pattern everyone reaches for exploits the media rule above. Google’s guidance on deferring non-critical CSS documents the preload form; the print variant is what plugins ship:
<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 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, and removing unused CSS 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 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 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, 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/
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, our fleet dashboard, runs the scan across every connected site, so the worst ones surface without opening twenty dashboards. WooCommerce stores 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 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 or the full capability 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:
- Run the free scan on your slowest template and note check A1’s file count and milliseconds.
- Extract your own
<head>and count what blocks. - Grep for
data-wp-strategywithoutdefer, and fix the dependent behind each hit. - 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.