It’s a common complaint when you run your WordPress site through any “page speed score” tool: “defer parsing of JavaScript” and/or “remove render-blocking JavaScript.” Today, building on an article Fred first wrote in 2015, I’m going to discuss a was to solve that. It’s been possible since WordPress 4.1, which introduced of a new filter, script_loader_tag
. This filter lets us easily change the HTML markup of enqueue
d script
elements—that is, of JavaScript files that were correctly added into a WordPress site using WordPress’s wp_enqueue_script
function.
With script_loader_tag
, we can now easily fix a problem that can significantly impact page speed: lots of render-blocking JavaScript.
The Problem: Render-Blocking JavaScript
Long JavaScript files in your
head
can delay your browser from displaying page content, because its default behavior is first to interpret the JS files themselves.
Properly enqueued JavaScript shows up in the head
section of your HTML document. On the internet as in nature, the main thing about a head
is that it’s above a body
—and this means something fairly serious for site speed, because JavaScript can be render-blocking.
“Render-blocking” comes from a web browser’s default behavior: It wants to completely receive and process everything that’s come higher up in the page, before it moves any further down.
This means that long JavaScript files in your head
can actually delay your browser from displaying the page content in the body
, because its default behavior is first to interpret the JS files themselves. In other words, JS is blocking the browser’s crucial function of rendering the page out for the user to actually see. The result can be slow sites and frustrated users.
Google’s Pagespeed Insights has been pointing out this issue for a while:
However, prior to 4.1 and script_loader_tag
, the only solution I knew of was to move scripts to the site’s footer. Move scripts to the footer is difficult to do by hand. And the plugins I tested that claim to do it automatically didn’t work the way I hoped.
Let’s move straight to our success story:
The Goal: Much Less Render-Blocking JS
Here’s what we got down to with the solution we’ll present below:
Now we’ve only got three JavaScript files that can possibly slow down a page’s rendering. The rest are still there, but they load in parallel with the page content, rather than before it.
The Fix: Defer and Async your JavaScript
The first thing to understand is the alternatives to render-blocking JS: defer
and async
. We’ll explain the difference, but both work similarly: They let the browser load a JS resource “as time permits,” while attending to other things (like page rendering) as well. This means that you can’t rely on a deferred or asynced JavaScript file being in place prior to page render, as you could without these attributes—but the advantage is that the file won’t slow the speed at which the page becomes visible to users.
Those are concepts—now for code. (The full code is available on GitHub.)
1. Getting Your Script Handles
Every properly enqueued WordPress script has a handle: a “nickname” that the site knows to call it by. We’re going to need these handles for all scripts, and getting them isn’t dead-simple, unfortunately.
It is possible, though:
/*
* Getting script tags
* Thanks http://wordpress.stackexchange.com/questions/54064/how-do-i-get-the-handle-for-all-enqueued-scripts
*/
add_action( 'wp_print_scripts', 'wsds_detect_enqueued_scripts' );
function wsds_detect_enqueued_scripts() {
global $wp_scripts;
foreach( $wp_scripts->queue as $handle ) :
echo $handle . ' | ';
endforeach;
}
This code prints out a list of enqueued handles, separated by |
, right into the head
of every page:
You’ll only do this once, then use “View Page Source” to copy and paste the handles themselves.
Once you’ve done this, deactivate this section of the code: we’ve got our handles, so let’s not clog up our head
with them anymore. That’s why this section is commented out in the code on GitHub—I don’t want it to run every time!
2. Deferring and Asyncing Render-Blocking JavaScript
We found that we needed to use defer
and not async
for WPShout, so I’ll walk through the defer
code. Most of the heavy lifting here is from an article by Scott Nelle; thanks, Scott!
add_filter( 'script_loader_tag', 'wsds_defer_scripts', 10, 3 );
function wsds_defer_scripts( $tag, $handle, $src ) {
// The handles of the enqueued scripts we want to defer
$defer_scripts = array(
'prismjs',
'admin-bar',
'et_monarch-ouibounce',
'et_monarch-custom-js',
'wpshout-js-cookie-demo',
'cookie',
'wpshout-no-broken-image',
'goodbye-captcha-public-script',
'devicepx',
'search-box-value',
'page-min-height',
'kamn-js-widget-easy-twitter-feed-widget',
'__ytprefs__',
'__ytprefsfitvids__',
'jquery-migrate',
'icegram',
'disqus',
);
if ( in_array( $handle, $defer_scripts ) ) {
return '<script src="' . $src . '" defer="defer" type="text/javascript"></script>' . "\n";
}
return $tag;
}
The add_filter
line tells us that this code should run anytime an enqueued JavaScript file is about to be printed onto the page as an HTML script
element. Letting us filter that HTML is what script_loader_tag
is for. (If you need an update on filters and WordPress’s hooks system in general, start here!)
The biggest check in this code is doing a single thing defining the $defer_scripts
array. This array lists out the handles of all the elements we want to defer—the handles we found in step 1. (Your handles will, of course, vary!)
The logic below the array definition (beginning with if ( in_array(
) searches for the current script’s handle attribute in the array we’ve just defined. If the handle matches an element in the array, then we modify the script to have the same source, but with a new property: defer="defer"
, which will cause the script not to block rendering. With this change made, we return
the HTML back, and we’re good to go!
(And finally, if the handle isn’t found, we just return the original tag itself, unaltered.)
You’ll know this plugin is working when you view page source and see something like this in the head
:
When to Use Async Instead of Defer
You use async
when you’re linking directly to an external JavaScript library. That link would look something like: <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
. Notice how it’s a link to the full URL, and the JavaScript will get pulled in
enqueue
ing external JS is a lot less common, at least for us, since most of our enqueued JS is in themes and plugins that host their own code. At any rate, the code for async
is precisely the same as the code for defer
—but with the two words switched out. So if you do happen to have a lot of externally hosted enqueue
d scripts, getting them async
ed is a very similar technical process to the one we’ve just covered.
Which Scripts to Defer and Async
You’ll notice that we didn’t defer everything—a few scripts are still render-blocking. Here are rules of thumb on that:
- Don’t do anything to jQuery. jQuery (handle
jQuery
) is a key dependency for many other JS files, and you want to let it load early. - Any file that’s wrapped in a
jQuery( document ).ready( function() { })
call should be fine to defer. That code basically says “Wait until the entire document object model (DOM) loads,” so racing to get the JavaScript file loaded in the head doesn’t serve much purpose. - In general, you can defer JavaScript files that rely on user interactions, like clicks and mouse hovers—and files that fix layout details, like center or hide a set of element. Again, these rely on a loaded page to work anyway (which is why they’re almost all going to be wrapped in
jQuery( document ).ready( function() { })
, or else they’re liable not to work), so you should be safe to get the page out beforehand. - It’s, unfortunately, impossible to use this method for JavaScript files that have been added some way other than the generally correct method of
enqueue
ing them. This is another reason to prefer that method over other ways of loading scripts that may appear to work fine at first glance.
Want a Simpler Solution? Defer JavaScript Parsing in WordPress with a Plugin
Because of how common this need to defer JavaScript parsing in WordPress, you can find already-written plugins to do it. The precise methodology then becomes a little less important to you. If you’re such an (understandably) hurried person, I’ve got your back.
A quick note for those hurried people: How well you’re able to defer JavaScript parsing with a simple plugin-activation will depend a great deal on the nature of your WordPress setup. If you’ve got a simple site with a few well-maintained plugins, I’m guessing that simply toggling on a plugin like Async JavaScript (from the maker of Autoptimize) is likely to work fine. But be aware that these plugin can cause (JavaScript-based) features to break in subtle ways that you might not notice for a while. (This is also true of when you’re writing and maintaining the code we discussed above, but you’ll probably do a more thorough test when you’ve written your code for deferring JavaScript parsing than when it was as easy for you as activating a plugin.)
So in both cases, tread carefully. The makers of these plugins have likely tested extensively, but because WordPress sites are so diverse and varied I’d test a fair amount before rolling with any of these JavaScript-deferring plugins. Here are the three that I found in my research:
- Async JavaScript (linked above) is by far the most popular, and by the people who’ve made the famous Autoptimize plugin. (We run Autoptimize here on WPShout)
- Speed Booster Pack — seems a little more more full-featured than the Async JavaScript plugin, and a bit less popular
- WP Deferred JavaScript — this one seems like it could be abandoned, but I’m including it here for completeness
Two Ways to Defer (or Async) your WordPress JavaScript
At its core we covered a very cool and rather quick way to improve your site speed and user satisfaction. Defering the parsing of JavaScript can make your WordPress site a great deal gaster without any notable impact on reading experience. I hope you now know enough to defer and async your own JavaScript files, and thanks for reading! I’d love to hear comments or questions below.
Can this be amended for css?
The concept’s a little different. What are you trying to accomplish exactly?
So I defer and async all my scripts including the jquery, and I get an 88/100 on mobile page speed. But I have issues due to deferring jquery. So when I exclude that one, my score goes all the way from 88/100 to 67/100.
There has to be a way to get better scores and that is such a huge hit.
Great article Fred, thanks!
Great article and real world demonstration. I cannot seem to get the ASYNC to work, I tried using the full url without the ?ver=1.5.2 and with the ?ver=1.5.2 at the end of the url…as it shows up on view page source. Neither works. Google Page Speed is still giving me a very bad report. Not sure what to try next…
Thanks for the nice post. Next: a plugin that does some or all of the steps?
Good idea! Have put on my “for free time” list 🙂
Nice post! Just noticing the filter example doesn’t show the modified script tag for async or defer, just an empty string.
Thanks, Miles! Should be fixed. That’s the page parsing rather than displaying HTML tags I accidentally left in our syntax highlighter. 🙂