Skip to content

Everything for WordPress, web development — and beyond

) with defer will be ignored, the browser will execute it as a normal blocking script. To delay inline code, wrap it in a DOMContentLoaded or load listener."}},{"@type":"Question","name":"Is it safe to put defer on all WordPress site scripts?","acceptedAnswer":{"@type":"Answer","text":"jQuery with defer will break any code calling $() or jQuery() before DOMContentLoaded. That's exactly why jQuery is excluded from processing in the snippet above. Start with non-critical scripts: chat, social media, ads. Expand the list gradually, checking the console for errors after each change."}},{"@type":"Question","name":"Intersection Observer or defer, which is better?","acceptedAnswer":{"@type":"Answer","text":"They solve different tasks. defer is for scripts that are always needed on the page but aren't critical for first render: analytics, A/B tests. Intersection Observer is for scripts tied to a specific block: map, chat in footer, comments widget. Simple rule: script in the upper part of the page, defer; script below the fold, Intersection Observer."}},{"@type":"Question","name":"What to do if Flying Scripts broke functionality?","acceptedAnswer":{"@type":"Answer","text":"Exclude the problematic script from the delay list in plugin settings. Flying Scripts allows specifying keywords for inclusion, remove the problematic script's keyword and it will load normally. For critical elements like forms and chats, this is a mandatory step before activating delay."}}],"inLanguage":"en"}]}
⏳ Deferred loading of external scripts in JavaScript: complete guide 2026

⏳ Deferred loading of external scripts in JavaScript: complete guide 2026

Seven out of ten sites that fail PageSpeed Insights slow down because of third-party JavaScript. External scripts, analytics, ad networks, chats, fonts, cookie banners block rendering and push First Contentful Paint back by seconds. And you can't disable them: analytics feeds marketing, chat brings leads, without a cookie banner the site won't pass an audit.

There's a solution: lazy loading. The script loads only when the main content has already been shown to the user. The browser doesn't wait, Core Web Vitals metrics go into the green zone, and site functionality doesn't suffer.

We've collected all working methods, from built-in HTML attributes to programmatic techniques and WordPress plugins. With code examples you can copy and apply today.

💡 Quick overview:

  • Understand defer and async: the table clearly shows the difference between attributes
  • Master programmatic loading on the load event: a modern version of Patrick Sexton's method
  • Set up Intersection Observer for scripts below the fold: chats, maps, comments
  • Optimize WordPress without editing code: Flying Scripts and Asset CleanUp plugins

How third-party scripts slow down loading

The browser parses HTML from top to bottom. When it encounters <script src="..."> without attributes, parsing stops: the browser loads the script, executes it, and only then continues parsing the page. This is render-blocking.

For the user, the result is a white screen. The First Contentful Paint (FCP) metric, the time until the first displayed content, directly depends on how quickly the browser got through all blocking scripts. FCP is part of Core Web Vitals and directly affects search ranking.

Third-party scripts hit harder than your own: they're hosted on external servers, and you don't control their delivery speed or availability. DNS query, TCP handshake, TLS handshake, download, each stage adds tens or hundreds of milliseconds. According to HTTP Archive data for 2024, the median site on mobile devices loads 21 external scripts, and three of them are blocking.

The solution is to give the browser a clear instruction: "load the script in the background and execute it later." That's exactly what the defer and async attributes are designed for.

defer and async: two built-in mechanisms

Both attributes are written in the <script> tag and change loading behavior. In fundamentally different ways.

Attribute

Load order

Execution moment

Execution order

(no attribute)

Blocks parsing

Immediately after loading

In HTML order

async

Parallel to parsing

Immediately after loading

Whichever loads first

defer

Parallel to parsing

After full HTML parsing

In HTML order

defer: delayed execution with order guarantee

The defer attribute tells the browser: "load the script in the background, execute after full HTML parsing." Execution order is preserved, scripts execute in exactly the sequence specified in the markup.

1<script src="https://example.com/analytics.js" defer></script>
2<script src="https://example.com/chat-widget.js" defer></script>

The ideal choice for scripts that must execute in a specific order: library, then its plugin, then initialization. defer guarantees that the DOM is ready by execution time. The DOMContentLoaded event fires after all defer scripts.

async: complete independence

async loads the script parallel to parsing and executes it immediately after loading, without waiting for others. Order is not guaranteed: whichever script loaded first from the server executes first.

1<script src="https://example.com/independent-widget.js" async></script>

Suitable for completely independent scripts: visit counters, social media buttons, ad banners. If the script doesn't depend on DOM and doesn't depend on other scripts, use async.

Important nuance: an async script can execute before the DOM is built. If it references page elements that don't exist yet, you'll get null and an error in the console. Always check.

Programmatic loading on the load event: Patrick Sexton's method

What if the script doesn't support defer/async or you don't control the markup? For example, the script is inserted through Google Tag Manager or hardcoded in someone else's plugin. The programmatic method comes to the rescue.

The idea is simple: create a <script> element via JavaScript and add it to the DOM only after the page has fully loaded. The author of the approach, Patrick Sexton, first described the technique on varvy.com.

The modern version of this code:

1function loadScriptOnPageLoad(src) {
2 const script = document.createElement('script');
3 script.src = src;
4 script.async = false;
5 document.body.appendChild(script);
6}
7
8window.addEventListener('load', () => {
9 loadScriptOnPageLoad('/wp-content/plugins/chat/chat.js');
10 loadScriptOnPageLoad('/wp-content/plugins/analytics/tracker.js');
11});

Two key differences from decade-old code. First: we listen to load, not DOMContentLoaded. The load event occurs later, when all images, styles, and fonts are loaded. A delayed script is guaranteed not to affect FCP or LCP. Second: no window.attachEvent. This method was only needed for Internet Explorer 8 and below, which no longer exist. Modern addEventListener works in all current browsers.

Three situations where the programmatic approach is irreplaceable

defer and async only work for <script> tags written directly in HTML. The programmatic method saves you when:

  • The script is inserted through Google Tag Manager or another tag manager, you don't see the markup.
  • You can't edit the template: someone else's plugin or theme with a rigid structure.
  • You need conditional loading: for example, a contact form script only on pages with a form.

Intersection Observer: on-demand loading

For some scripts, even defer is too early. A chat widget isn't needed until the user scrolls to the footer. A map, until they reach the address block. Comments, until they read to the discussion section.

Intersection Observer API solves exactly this task: the script loads only when the target element appears in the viewport.

1const chatTarget = document.getElementById('chat-container');
2
3if (chatTarget) {
4 const observer = new IntersectionObserver((entries) => {
5 entries.forEach((entry) => {
6 if (entry.isIntersecting) {
7 const script = document.createElement('script');
8 script.src = '/wp-content/plugins/chat/chat.js';
9 document.body.appendChild(script);
10 observer.unobserve(entry.target);
11 }
12 });
13 }, { rootMargin: '200px' });
14
15 observer.observe(chatTarget);
16}

The rootMargin: '200px' parameter loads the script 200 pixels before the element appears on screen, the user doesn't notice a delay. Previously, developers used scroll listeners with getBoundingClientRect() for years, but those fired on every pixel of scrolling and overloaded the main thread.

Intersection Observer works asynchronously and puts almost no load on the browser. Support is in all modern browsers, including Safari from version 12.1. IE11 is not supported, but its share as of June 2026 approaches statistical error.

WordPress: lazy loading without editing code

If the site is on WordPress, you can implement lazy loading in three ways, from simplest to most flexible.

Plugins for lazy loading scripts

Two working options, tested on thousands of sites.

Flying Scripts, a free plugin from Gijo Varghese, 30,000+ active installations, version 1.2.4 (updated in May 2026). Can delay JavaScript until first user interaction: mouse movement, click, scroll, touch on mobile. You specify keywords from the script URL, and the plugin delays its execution. There's a timeout, if the user doesn't interact with the page, scripts execute after a set time. Maximum metric gain, but chat or callback form should be excluded from delay, they're needed immediately.

WP Rocket, a premium caching plugin with built-in JavaScript loading delay functionality. Adds defer or async to selected scripts through the admin interface. You specify the URL, the plugin changes the attribute when rendering the page. Suitable for those already using WP Rocket for caching and don't want to multiply plugins.

script_loader_tag hook in functions.php

If you don't want to install a separate plugin, WordPress allows filtering <script> tag output through the script_loader_tag hook. The code below adds defer to all scripts except jQuery and the admin panel:

1add_filter('script_loader_tag', function($tag, $handle) {
2 if (is_admin()) {
3 return $tag;
4 }
5 $skip = ['jquery', 'jquery-core', 'jquery-migrate'];
6 if (in_array($handle, $skip, true)) {
7 return $tag;
8 }
9 return str_replace(' src', ' defer src', $tag);
10}, 10, 2);

Place the code in the child theme's functions.php or through the Code Snippets plugin. Before applying, make a backup. defer on jQuery will break any code that calls $() or jQuery() before DOMContentLoaded. Start with individual scripts, check the console for errors after each change.

Full control: Asset CleanUp

Asset CleanUp, a free plugin (version 1.4.0.4, updated in May 2026) that shows ALL scripts and styles loaded on a page. With size and source indicated. You can disable a specific script on a specific page, change the loading attribute to defer/async, or completely unload an unnecessary asset.

The main advantage over Flying Scripts: Asset CleanUp gives a complete picture for each page. You see what exactly is loading and make decisions precisely. The Pro version adds conditional loading by screen type and moving scripts between HEAD and BODY. Works alongside any caching plugin, WP Rocket, W3 Total Cache, WP Fastest Cache.

A deeper dive into WordPress acceleration is in a separate article: 21 tips for improving PageSpeed Insights scores. If the problem isn't just scripts but overall hosting speed, check out ways to reduce WordPress page load time. And tools for checking WordPress performance will help measure the real effect of optimization.

Video: async and defer in practice

A five-minute breakdown from the xplodivity channel, with visual loading diagrams and live code examples:

⁉️🤔 Frequently asked questions

defer** or async, which to choose for Google Analytics?**

async. Analytics.js and gtag.js are completely independent of the DOM, they don't need execution order. GA4 officially recommends async, the library handles delayed initialization itself.

How does the load event differ from DOMContentLoaded?

DOMContentLoaded fires when HTML is fully parsed and the DOM tree is built, styles and images may still be loading. load occurs later: when absolutely everything is loaded, including images, fonts, and stylesheets. For lazy script loading, load is safer: the page has definitely been shown to the user by this point.

Can you add defer to an inline script?

No. The defer attribute only works for external scripts with the src attribute. An inline script (<script>code</script>) with defer will be ignored, the browser will execute it as a normal blocking script. To delay inline code, wrap it in a DOMContentLoaded or load listener.

Is it safe to put defer on all WordPress site scripts?

jQuery with defer will break any code calling $() or jQuery() before DOMContentLoaded. That's exactly why jQuery is excluded from processing in the snippet above. Start with non-critical scripts: chat, social media, ads. Expand the list gradually, checking the console for errors after each change.

Intersection Observer or defer, which is better?

They solve different tasks. defer is for scripts that are always needed on the page but aren't critical for first render: analytics, A/B tests. Intersection Observer is for scripts tied to a specific block: map, chat in footer, comments widget. Simple rule: script in the upper part of the page, defer; script below the fold, Intersection Observer.

What to do if Flying Scripts broke functionality?

Exclude the problematic script from the delay list in plugin settings. Flying Scripts allows specifying keywords for inclusion, remove the problematic script's keyword and it will load normally. For critical elements like forms and chats, this is a mandatory step before activating delay.

What to put on your site: decision matrix

The choice comes down to a simple decision table:

  • Script is written in HTML, you control the tags, defer if depends on order, async if completely independent.
  • Script is inserted via GTM or someone else's plugin, tags are inaccessible, programmatic loading on the load event.
  • Script is tied to a block below the fold, Intersection Observer with rootMargin: '200px'.
  • WordPress, need it without code, Flying Scripts (free, 30,000+ sites) or Asset CleanUp for full asset control.

Start with the simplest: open PageSpeed Insights, find blocking external scripts, and add defer to them. One attribute, and FCP can go from red to green. And when you've mastered attributes, check out our review of tools for checking WordPress performance, it will help measure the real effect.