/ โ† JavaScript Checker Fixes / Render-Blocking JS
In this guide
  1. What makes a script block rendering
  2. defer, async and module: what each one changes
  3. How to find the blocking scripts
  4. The fix, step by step
  5. Before and after: a typical theme head
  6. Snippets that deliberately hold the page back
  7. Platform notes
  8. Edge cases
  9. Questions and answers

What makes a script block rendering

The HTML parser reads your page from top to bottom. When it meets a classic script tag with a src and no async, defer or type="module", it has to assume the script might change what comes next, for example by writing into the page. So it pauses: nothing below the tag is parsed until the file has downloaded and run. When that tag is in the head, nothing in the body exists yet, so there is nothing to paint.

Three details make this worse than it sounds:

Inline scripts, written directly in the page, do not need a download, but they still stop the parser while they run, and they still wait for stylesheets above them. A long inline script in the head blocks just like an external one.

๐Ÿ’ก Scripts added to the page by other JavaScript, such as the standard loader snippets for analytics and tag managers, are fetched without blocking the parser. What blocks is the script tag written literally into the HTML.

defer, async and module: what each one changes

TagParser waits?Runs whenOrder kept?Use it for
Classic script, no attributeYes, for download and runImmediatelyYesAlmost nothing in the head; very small inline code that must run first
deferNoAfter parsing finishes, before DOMContentLoadedYes, in document orderSite scripts, libraries and anything that uses the page
asyncOnly while it runsAs soon as it has downloadedNoSelf-contained scripts with no dependants: analytics, tag managers, some widgets
type="module"NoLike deferYesModern code using import
type="module" asyncOnly while it runsAs soon as it and its imports are readyNoIndependent modules

Some rules that trip people up:

How to find the blocking scripts

The JavaScript Checker flags render-blocking scripts on each page you test and lists every script with its size and parse and execution time, so you can see which blockers are also expensive to run. PageSpeed Insights reports the same problem in its render-blocking requests insight, listing stylesheets and scripts together with an estimate of the delay; read the file names to separate the two.

In Chrome, you can ask the browser directly which script files it treated as blocking on the load you just made:

performance.getEntriesByType('resource')
  .filter((r) => r.initiatorType === 'script' && r.renderBlockingStatus === 'blocking')
  .forEach((r) => console.log(r.name, Math.round(r.responseEnd - r.startTime), 'ms'));

Then view the page source, not the Elements panel, and search for <script in the head. For every tag, note whether it has a src, whether it has defer, async or a module type, and whether any inline code below it depends on it. That list is your working plan. On CMS sites, check several templates: home, category, product and blog posts often load different plugin scripts.

โš ๏ธ The Elements panel shows the page after scripts have run and may include tags that were injected later. Only the page source, or curl, shows the tags the parser actually met.

The fix, step by step

Step 1

List the scripts that block the first paint

Run the page through the JavaScript Checker and note every script it flags as render-blocking, with its size and execution time. Then open the page source and write down each script tag in the head and body: its address, whether it is inline, and which of defer, async or type="module" it has. Mark any inline code that calls a function from one of those files.

Step 2

Decide what truly must run before first paint

Very little belongs before the first paint: a tiny inline snippet that sets a class for a saved dark theme to prevent a flash, a consent default that must exist before any tag runs, or a small feature check. Keep those inline and short. Everything else, including sliders, menus, forms, analytics, chat and reviews, can wait until after the page is visible and parsed.

Step 3

Defer scripts that work with the page

Add defer to every external script that reads or changes the page, including libraries such as jQuery and the scripts that depend on them. Keep them in the same order, since deferred scripts run in document order, and leave them in the head so the downloads start early. Test menus, forms, galleries and the basket after the change, because this is where hidden dependencies show up.

Step 4

Reserve async for independent scripts

Use async only for scripts that nothing else calls and that do not need the page to be complete, such as analytics or a tag manager container. Never make a library async if other code depends on it, because it may run before or after its dependants at random. When unsure, choose defer, which is almost never slower for first paint and keeps the order predictable.

Step 5

Rewire inline code that calls deferred libraries

Inline scripts run immediately, so an inline call to a deferred library fails with a not defined error. Move that code into its own file loaded with defer after the library, or wrap it in a DOMContentLoaded listener, which fires only after deferred scripts have run. For jQuery ready calls in themes, the listener wrapper is usually the quickest safe fix.

Step 6

Remove document.write and page-hiding snippets

Replace any snippet that uses document.write to add scripts with the vendor's current asynchronous loader, since Chrome may block such scripts on slow connections anyway. Look for A/B testing or personalisation snippets that hide the page until a script loads, often called anti-flicker code, and either remove them, cut their timeout sharply, or limit them to the pages running a live test.

Step 7

Preload critical scripts the browser finds late

If a script needed early is only discovered late, for example because another script adds it or it is imported by a module several levels deep, add link rel="preload" as="script" or link rel="modulepreload" in the head so the download starts straight away. Preload only one or two genuinely important files, because every preload competes with the main image and stylesheets for bandwidth.

Step 8

Check first paint on a throttled connection

In the Performance panel, set network throttling to a slow 4G profile and CPU throttling to 4x, record a reload before and after your changes, and compare when the first paint happens. Re-run the JavaScript Checker to confirm no scripts are still flagged as render-blocking, then click through the key journeys on a real phone to make sure nothing broke.

Before and after: a typical theme head

This head, based on a common older theme pattern, blocks the first paint three times: once for the library, once for the slider plugin and once for the inline code.

<!-- Before -->
<head>
  <link rel="stylesheet" href="/css/site.css">
  <script src="/js/jquery.min.js"></script>
  <script src="/js/slider.min.js"></script>
  <script>
    $(function () { $('.hero-slider').slider({ speed: 400 }); });
  </script>
  <script src="https://widgets.example.net/reviews.js"></script>
</head>
<!-- After -->
<head>
  <link rel="stylesheet" href="/css/site.css">
  <script src="/js/jquery.min.js" defer></script>
  <script src="/js/slider.min.js" defer></script>
  <script src="/js/home.js" defer></script>
  <script src="https://widgets.example.net/reviews.js" async></script>
</head>

// /js/home.js - runs after jQuery and the slider, in order
$('.hero-slider').slider({ speed: 400 });

The inline code moved into a small deferred file listed after the libraries it needs. The reviews widget depends on nothing and nothing depends on it, so it became async. If you cannot move inline code into a file, for example because a CMS prints it, wrap it instead:

document.addEventListener('DOMContentLoaded', function () {
  $('.hero-slider').slider({ speed: 400 });
});

Snippets that deliberately hold the page back

A few kinds of script are designed to run before the page shows. Each is worth questioning.

A/B testing and personalisation

Client-side testing tools change the page after it loads, so they often ship an anti-flicker snippet that hides the whole page until the testing script has run or a timeout expires. That can add seconds of blank screen for every visitor, including those not in any test. Options, from best to least effective: run tests on the server or at the CDN edge instead; load the snippet only on pages with a live test; or cut the timeout to the shortest value the vendor supports. Remove the snippet completely when a testing tool is retired; old anti-flicker code for discontinued tools is still found on many sites.

Consent tools

Consent management platforms usually ask to be placed first in the head. If you use Google consent mode, the small inline block that sets the default consent state does need to run before tags load, and it is tiny. The banner script itself rarely needs to block; check whether your provider offers an asynchronous loading option and test that tags still respect the stored choice when you use it. This is not legal advice; check your consent set-up with whoever is responsible for compliance before changing the order.

document.write

Older advertising and widget snippets use document.write to insert further scripts. Those inserted scripts block the parser, and Chrome may refuse to load cross-origin scripts inserted this way for users on slow connections, so the snippet can both slow the page and fail. Browsers also ignore document.write called from a deferred or async script. Ask the vendor for their current loader.

Platform notes

WordPress

Since WordPress 6.3, themes and plugins can ask for a loading strategy when they register a script, by passing an array such as array( 'strategy' => 'defer', 'in_footer' => false ) as the last argument of wp_register_script or wp_enqueue_script. WordPress then works out a safe strategy based on dependencies, falling back to blocking if a dependant needs it. Themes and plugins that do not use this still print blocking tags; performance plugins can add defer for you, but exclude jQuery and anything that inline code calls if pages break, and test page builders carefully.

Shopify

The script_tag Liquid filter outputs a plain blocking tag. In theme.liquid and section files, write the tag yourself with the asset_url filter for the address and add defer. Scripts from apps arrive through app embeds and content_for_header; you cannot edit those tags directly, so switch off app embeds you do not use and raise blocking scripts with the app developer.

Frameworks and static sites

Bundlers such as Vite output module scripts, which are deferred by default, so the main bundle rarely blocks. Blocking tags usually come from third-party snippets pasted into the base template. In Next.js, load those through next/script with the afterInteractive or lazyOnload strategy rather than a raw tag in the document head.

Edge cases

Questions and answers

1. What is render-blocking JavaScript?

It is any script the browser must download and run before it can carry on building and showing the page. In practice that means a classic script tag in the HTML with a src and no defer, async or module type, plus any inline script in the head. While the browser waits, the visitor sees a blank or partly drawn screen. Adding defer or async, or moving the code, lets the page appear first and the script run afterwards.

2. Should I use defer or async?

Use defer for almost everything that works with the page or depends on another script, because deferred scripts wait until the HTML is parsed and then run in the order they appear. Use async only for self-contained scripts that nothing else calls, such as analytics or a tag manager, since async scripts run as soon as they arrive, in any order. If you are unsure, defer is the safer choice and is rarely any slower for the first paint.

3. Why did adding defer break my site?

Usually because inline code somewhere on the page calls a function from the script you deferred. Inline scripts run immediately, before deferred scripts, so the function does not exist yet and the console shows a not defined error. Move that inline code into a small deferred file listed after the library, or wrap it in a DOMContentLoaded event listener, which only fires after all deferred scripts have run. Then test menus, forms and sliders again.

4. Is putting scripts at the bottom of the page enough?

It helps, because content above the script can be parsed and shown first, and for years it was the standard advice. But the parser still stops at the script, so anything after it waits, and the download only starts when the parser reaches the tag. Keeping the script in the head with defer usually does better, because the file starts downloading straight away while the parser carries on, and it still runs after the HTML is ready.

5. Do Google Tag Manager and analytics block rendering?

The standard loader snippets for Google Tag Manager and Google Analytics insert their main script asynchronously, so they do not block the parser, although the small inline part of the snippet runs where it is placed. They can still slow the page later by running a lot of code once loaded, which is a separate problem from render-blocking. Tags added inside the container that use document.write or hide the page are the ones to review.

6. Is it safe to defer jQuery?

Yes, if everything that uses jQuery is also deferred or waits for the DOMContentLoaded event. Deferred scripts run in document order, so jQuery followed by plugins that need it works as before. What breaks is inline code in templates or page builder blocks that calls jQuery immediately. On WordPress, find those calls first, or exclude jQuery from deferral in your performance plugin if a page builder relies on inline calls you cannot change.

7. What is an anti-flicker snippet and should I remove it?

It is a piece of code supplied by some client-side A/B testing and personalisation tools that hides the whole page until the testing script has loaded or a timeout passes, so visitors do not see the original version change. It delays the first paint for everyone, including visitors not in a test. Remove it if the tool is no longer used, limit it to pages with a live test, shorten the timeout, or move testing to the server.

Find the scripts holding up your page

The JavaScript Checker loads your page in headless Chromium, flags render-blocking scripts, and lists every script with its size, parse and execution time, alongside third-party tags and console errors.

Open JavaScript Checker Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All JavaScript Checker Fixes  ·  Fixing Render-Blocking CSS  ·  Fixing Long Tasks on the Main Thread  ·  Fixing Third-Party Tag Bloat  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.