---
title: Serve assets from Shopify CDN
description: >-
  Serve all theme assets through the Shopify CDN. Place static files in the
  `assets/` directory and reference them with `asset_url`, and pass Shopify
  image objects through `image_url` and `image_tag`, to eliminate a connection
  setup for every external domain.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-shopify-cdn
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-shopify-cdn.md
api_name: liquid
---

# Serve assets from Shopify CDN

Serve all theme assets from the Shopify CDN instead of external domains to eliminate connection overhead for each domain.

***

## Why

Shopify serves theme assets, such as CSS, JavaScript, fonts, and images, through its CDN. Storefront asset URLs use your store's own domain under a `/cdn` path, for example `/cdn/shop/files/hero.jpg`. Same-origin assets reuse the browser's existing connection to the store.

Every additional domain a page references requires a fresh connection before its first byte arrives: a DNS lookup, a TCP handshake, and a TLS handshake, which together take up to three network round trips. On a mobile connection with 150 ms of latency, this results in a delay of nearly half a second before the asset starts downloading, and each extra domain repeats it. When the externally hosted file is a render-blocking stylesheet or the hero image, that delay directly increases First Contentful Paint and Largest Contentful Paint (LCP).

You don't need to hard-code the `cdn.shopify.com` domain in your theme. Use Liquid URL filters, and Shopify renders the appropriate asset host for the request.

***

## How

Move externally hosted files that are part of your theme into the theme's `assets/` directory. Reference them by filename with the [`asset_url`](https://shopify.dev/docs/api/liquid/filters/asset_url) filter instead of hard-coding a CDN domain. Shopify chooses the appropriate asset host and appends a version parameter when it renders the theme.

### Choose the correct filter for each image source

Both filters serve from the Shopify CDN. Which one you use depends on where the image lives, and that choice determines whether you can resize the image at request time.

Images that ship with the theme live in `assets/` and are referenced with `asset_url`. Shopify serves these files as-is, so you author the `<img>` tag yourself and set `alt`, `width`, and `height` explicitly. Without `width` and `height` the browser can't reserve layout space, and the image shifts the page as it loads. Reserve this path for fixed-size assets that don't need resizing, such as a logo or an icon.

Images that a merchant supplies aren't theme files. They come from an [`image_picker` setting](https://shopify.dev/docs/storefronts/themes/architecture/settings/input-settings#image_picker) or an upload through **Files**, and they arrive at whatever dimensions the merchant chose. Pass these through [`image_url`](https://shopify.dev/docs/api/liquid/filters/image_url) and [`image_tag`](https://shopify.dev/docs/api/liquid/filters/image_tag), which resize the image on the CDN and generate `srcset`, `alt`, `width`, and `height` for you. To describe how wide the image displays in the layout, pass a `sizes` option to `image_tag`.

An oversized image in `assets/` can't be resized, so if a static image needs responsive transformations, then move it out of `assets/` and upload it through **Files** instead.

`asset_url | image_tag` isn't supported. `asset_url` returns a URL string, but `image_tag` requires the image metadata and transformations returned by `image_url`.

### Automatic asset versioning

The `asset_url` filter appends version numbers to URLs. When you update a file, the version number changes, forcing browsers to download the new version.

```liquid
{{ 'style.css' | asset_url }}
{%- comment -%} Outputs: /cdn/.../style.css?v=1384022871 {%- endcomment -%}
```

Without versioning, browsers might cache outdated assets for days.

### CSS background images

For images referenced in CSS, use `asset_url` in a `.css.liquid` file:

## assets/style.css.liquid

```liquid
.hero {
  background: url({{ 'bg.jpg' | asset_url }});
}
```

The source file must have a `.css.liquid` extension for Shopify to process the Liquid code inside it. Reference it as `style.css`, without the `.liquid` extension. Shopify serves the compiled asset under that name.

***

## Examples

### Static theme assets

Replace hard-coded external domains with `asset_url`, using markup appropriate to each asset type:

```liquid
{%- comment -%} Before: three external domains, three connection setups {%- endcomment -%}
<link rel="stylesheet" href="https://cdn.example-ui.com/example-ui@3/example.css">
<script src="https://scripts.example-cdn.com/slider-3.7.1.min.js"></script>
<img src="https://images.example-cdn.com/logo.svg" alt="Store logo" width="240" height="80">


{%- comment -%} After: same-origin, versioned, and no extra connections {%- endcomment -%}
{{ 'theme.css' | asset_url | stylesheet_tag }}


<script src="{{ 'theme.js' | asset_url }}" type="module"></script>


<img
  src="{{ 'logo.svg' | asset_url }}"
  alt="Store logo"
  width="240"
  height="80"
/>
```

The `stylesheet_tag` filter generates the stylesheet link. The module script doesn't block HTML parsing. The static image includes dimensions to reserve layout space before it loads, and descriptive `alt` text for assistive technology. To host fonts in `assets/`, refer to [Self-host web fonts on Shopify CDN](https://shopify.dev/docs/storefronts/themes/best-practices/performance/self-host-web-fonts).

If you can't move a third-party asset into `assets/`, then add a [`preconnect` hint](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-preconnect) so the browser starts the connection early instead of discovering the domain while it parses the HTML.

### Responsive Shopify image

For a merchant-supplied image, the `image_url` filter applies CDN transformations, and `image_tag` generates the `srcset` and intrinsic dimensions. Pass the `sizes` option to describe how wide the image displays in the layout:

```liquid
{{ images['hero.jpg']
  | image_url:
      width: 1600,
      height: 900,
      crop: 'center'
  | image_tag:
      widths: '400, 800, 1200, 1600',
      sizes: '100vw',
      alt: 'Model wearing the summer collection'
}}
```

This example omits [`quality`](https://shopify.dev/docs/api/liquid/filters/image_url#image_url-quality), which accepts an integer from `10` to `90`. Omit it unless you've measured a smaller file at a specific value. Shopify selects a quality automatically based on the output format, and the automatic result is often smaller than an explicit high value.

For more image patterns, refer to the following guides:

* [Use Liquid filter chains instead of manual URL construction](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-filter-chains).
* [Serve correctly sized images with `srcset` and `sizes`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-responsive-images).
* [Use `image_url` and `image_tag` instead of manual CDN URLs](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-shopify-image-filters-efficiently).

***

## Testing

* In the [Chrome DevTools Network panel](https://developer.chrome.com/docs/devtools/network), add the **Domain** column from the table header context menu, then sort by it to list every host the page contacts. Audit every host other than your store domain, including origins that Shopify already preconnects to. Move files that you control into `assets/`. Exclude app domains and third-party services that don't allow copying files or require dynamic updates.
* Select an asset request and open its **Timing** tab. Same-origin CDN assets report `0 ms` for **DNS Lookup**, **Initial connection**, and **SSL**. Non-zero values there are the overhead you remove by moving the file into `assets/`.
* Record a **Performance** panel trace before and after the change, then compare Largest Contentful Paint and check whether the LCP resource's load delay shrinks.

***

## References

* [`asset_url`](https://shopify.dev/docs/api/liquid/filters/asset_url) filter
* [`image_url`](https://shopify.dev/docs/api/liquid/filters/image_url) filter
* [`image_tag`](https://shopify.dev/docs/api/liquid/filters/image_tag) filter
* [Using the Shopify CDN for better performance](https://performance.shopify.com/blogs/blog/using-shopify-cdn-for-better-performance)
* [The Shopify platform: CDN](https://shopify.dev/docs/storefronts/themes/best-practices/performance/platform#shopify-cdn)
* [Self-host web fonts on Shopify CDN](https://shopify.dev/docs/storefronts/themes/best-practices/performance/self-host-web-fonts)
* [Avoid request proxies](https://shopify.dev/docs/storefronts/themes/best-practices/performance/avoid-request-proxies)
* [Warm up third-party connections early with `preconnect`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-preconnect)

***
