---
title: Use defer and async on non-critical scripts
description: >-
  Use the `defer` attribute on non-critical scripts to prevent them from
  blocking the HTML parser. Use `async` only for independent, third-party
  scripts where execution order doesn't matter.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/defer-scripts
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/defer-scripts.md
api_name: liquid
---

# Use defer and async on non-critical scripts

Use the `defer` attribute on non-critical scripts to prevent them from blocking the HTML parser. Use `async` only for independent, third-party scripts where execution order doesn't matter.

***

## Why

When the browser encounters a `<script src="">` tag without `defer` or `async`, it stops parsing HTML, downloads the script, executes it, and then resumes parsing. Nothing below that script tag is discovered or rendered until the script finishes.

This has two costs:

1. **Delayed rendering**. The browser can't paint anything that depends on HTML below the blocking script. This delays [FCP](https://web.dev/fcp/) and [LCP](https://web.dev/lcp/).
2. **Blocked resource discovery**. The browser's preload scanner finds images, stylesheets, and other resources by scanning ahead through the HTML. A blocking script pauses that scan, so resources further down the page aren't fetched until the script completes.

Adding `defer` or `async` lets the browser continue parsing HTML while the script downloads, keeping the preload scanner working and allowing earlier paint.

***

## How

### Use `defer` for theme scripts

Scripts with `defer` download in the background and execute in document order after the HTML finishes parsing, but before the `DOMContentLoaded` event fires. This makes `defer` the right choice for most theme scripts because:

* Scripts that depend on the DOM can rely on the full document being available.
* Scripts that depend on each other run in the order that they appear in the HTML.
* The browser parses the entire document without interruption.

```liquid
<head>
  <script src="{{ 'theme.js' | asset_url }}" defer></script>
  <script src="{{ 'product-form.js' | asset_url }}" defer></script>
  <script src="{{ 'cart-drawer.js' | asset_url }}" defer></script>
</head>
```

All three scripts download in parallel while the HTML parses. They execute in order after parsing completes.

### Use `async` for independent third-party scripts

Scripts with `async` download in the background and execute as soon as they finish downloading. There's no guaranteed execution order, and execution can still briefly block the main thread.

Use `async` for scripts that are fully independent, don't rely on the DOM, and don't depend on other scripts. Analytics and tracking pixels are the most common examples.

```liquid
<script src="https://analytics.example.com/tracker.js" async></script>
```

If a third-party script does depend on the DOM or on other scripts, then use `defer` instead.

### Know what not to defer

Some scripts must run synchronously by design:

* **A/B testing anti-flicker snippets**. These hide page content until a variant is selected. Deferring them causes a flash of the original content before the variant applies. If you aren't actively running a test, [remove the snippet entirely](https://shopify.dev/docs/storefronts/themes/best-practices/performance/disable-ab-testing-when-inactive).
* **Scripts that modify content in the initial viewport before render**. If a script rewrites DOM that's visible without scrolling, then deferring it causes a visible flash or layout shift.

### Remove scripts that aren't needed

Before deciding whether to defer a script, ask whether it's needed at all. Every script, even deferred, consumes bandwidth and main-thread time during execution. Removing unnecessary scripts is always better than deferring them.

See [Audit and remove costly third-party scripts](https://shopify.dev/docs/storefronts/themes/best-practices/performance/audit-remove-third-party-scripts) for guidance on evaluating third-party script cost.

For scripts tied to specific user interactions, such as chat widgets, video players, and dialogs, consider loading them only when the user interacts with the feature. See [Load JavaScript on user interaction](https://shopify.dev/docs/storefronts/themes/best-practices/performance/load-javascript-on-user-interaction) for that pattern.

***

## Examples

### Example 1: Theme script loading in `theme.liquid`

Before: Synchronous scripts in `<head>` block HTML parsing.

```liquid
<head>
  <script src="{{ 'vendor.js' | asset_url }}"></script>
  <script src="{{ 'theme.js' | asset_url }}"></script>
  <script src="{{ 'product-form.js' | asset_url }}"></script>
</head>
```

After: Deferred scripts download in parallel and execute in order after parsing.

```liquid
<head>
  <script src="{{ 'vendor.js' | asset_url }}" defer></script>
  <script src="{{ 'theme.js' | asset_url }}" defer></script>
  <script src="{{ 'product-form.js' | asset_url }}" defer></script>
</head>
```

### Example 2: Third-party analytics

Before: A synchronous analytics script in `<head>` blocks page rendering.

```html
<head>
  <script src="https://analytics.example.com/tracker.js"></script>
</head>
```

After: `async` lets the browser continue parsing while the script downloads. Because the analytics script is independent and doesn't rely on execution order, `async` is appropriate here.

```html
<head>
  <script src="https://analytics.example.com/tracker.js" async></script>
</head>
```

### Example 3: Conditional script loading

Not every page needs every script. Use the Liquid `template` object to load scripts only on pages that require them. This reduces both download and execution cost on pages where the script does nothing.

```liquid
<head>
  {%- comment -%} Core theme scripts load on every page {%- endcomment -%}
  <script src="{{ 'theme.js' | asset_url }}" defer></script>


  {%- comment -%} Load product form logic only on product pages {%- endcomment -%}
  {% if template.name == 'product' %}
    <script src="{{ 'product-form.js' | asset_url }}" defer></script>
  {% endif %}


  {%- comment -%} Load the cart drawer only when the cart type is drawer {%- endcomment -%}
  {% if settings.cart_type == 'drawer' %}
    <script src="{{ 'cart-drawer.js' | asset_url }}" defer></script>
  {% endif %}


  {%- comment -%} Load search predictions only on pages with a search form {%- endcomment -%}
  {% if template.name == 'search' or settings.predictive_search_enabled %}
    <script src="{{ 'search.js' | asset_url }}" defer></script>
  {% endif %}
</head>
```

***

## Testing

* **Network panel waterfall**: Open Chrome DevTools, go to the **Network** panel, and reload the page. Synchronous scripts show a solid bar that blocks subsequent resource loading. Deferred scripts download in parallel with other resources.
* **Performance panel, Insights tab**: Record a page load and look for **Render blocking requests** in the summary. Any script without `defer` or `async` appears here.
* **Before and after comparison**: Record a **Performance** panel trace with synchronous scripts, add `defer`, then record again. Compare FCP and LCP between the two recordings.

***

## References

* [`asset_url`](https://shopify.dev/docs/api/liquid/filters/asset_url) filter
* [`script_tag`](https://shopify.dev/docs/api/liquid/filters/script_tag) filter
* [`template`](https://shopify.dev/docs/api/liquid/objects/template) object
* [`settings`](https://shopify.dev/docs/api/liquid/objects/settings) object
* [Debugging common causes for slow loading in Shopify Liquid storefronts](https://performance.shopify.com/blogs/blog/debugging-common-causes-for-slow-loading-in-shopify-liquid-storefronts)
* [Load JavaScript on user interaction](https://shopify.dev/docs/storefronts/themes/best-practices/performance/load-javascript-on-user-interaction)
* [Manage render-blocking resources strategically](https://shopify.dev/docs/storefronts/themes/best-practices/performance/defer-non-critical-resources)
* [Manage A/B testing performance impact](https://shopify.dev/docs/storefronts/themes/best-practices/performance/disable-ab-testing-when-inactive)
* [Audit and remove costly third-party scripts](https://shopify.dev/docs/storefronts/themes/best-practices/performance/audit-remove-third-party-scripts)

***
