---
title: Use image_url and image_tag instead of manual CDN URLs
description: >-
  Use `image_url` and `image_tag` together instead of constructing image markup
  manually to get automatic optimization, correct attributes, and Early Hints
  support in a single server-side operation.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-shopify-image-filters-efficiently
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-shopify-image-filters-efficiently.md
api_name: liquid
---

# Use image\_​url and image\_​tag instead of manual CDN URLs

Use Shopify's built-in image optimization capabilities instead of manually constructing image markup, to ensure consistent performance optimizations across your theme.

***

## Why

The server is much more efficient at building out the required image sizes than constructing them one at a time. When you manually create a URL for each width in a `srcset`, each width requires a separate filter call, which adds significant Liquid rendering overhead. A single `image_tag` filter builds all `srcset` widths on the server in one operation. This dramatically reduces TTFB and provides consistent optimizations for `width` and `height` attributes, focal point positioning, format negotiation, and CDN cache parameters.

Early Hints (HTTP 103) support on LCP images sends the preload instruction before the HTML finishes rendering, which lets the browser begin downloading the image earlier than any other discovery mechanism.

***

## How

For your LCP image, use all available optimizations. Gate the optimizations, not the image itself, so that the hero always renders:

```liquid
{% unless section.index > 1 %}
  {{ section.settings.hero_image
    | image_url: width: 2400
    | image_tag:
        widths: '800, 1200, 1600, 2000, 2400',
        sizes: '100vw',
        loading: 'eager',
        fetchpriority: 'high',
        preload: true
  }}
{% else %}
  {{ section.settings.hero_image
    | image_url: width: 2400
    | image_tag:
        widths: '800, 1200, 1600, 2000, 2400',
        sizes: '100vw',
        loading: 'lazy'
  }}
{% endunless %}
```

The condition tests `section.index > 1` rather than `section.index == 1` because `section.index` is `nil` in the online store editor, in static sections, and in the [Section Rendering API](https://shopify.dev/docs/api/section-rendering). Comparisons against `nil` are falsey, so `{% if section.index == 1 %}` with no `else` renders nothing in those contexts. For the full explanation, refer to [Load above-the-fold and below-the-fold sections differently with `section.index`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-section-index).

For other images, adjust based on position:

```liquid
{%- unless section.index > 2 -%}
  {%- assign image_loading = 'eager' -%}
{%- else -%}
  {%- assign image_loading = 'lazy' -%}
{%- endunless -%}
{{ product.featured_image
  | image_url: width: 800
  | image_tag:
      widths: '400, 600, 800',
      sizes: '(min-width: 750px) 25vw, 50vw',
      loading: image_loading
}}
```

***

## Examples

### Product grid

```liquid
{%- for product in collection.products limit: 12 -%}
  <div class="product-card">
    {%- if forloop.index <= 4 -%}
      {%- assign card_loading = 'eager' -%}
    {%- else -%}
      {%- assign card_loading = 'lazy' -%}
    {%- endif -%}
    {{ product.featured_image
      | image_url: width: 600
      | image_tag:
          widths: '300, 400, 500, 600',
          sizes: '(min-width: 990px) 25vw, (min-width: 750px) 33vw, 50vw',
          loading: card_loading
    }}
  </div>
{%- endfor -%}
```

### Carousel

```liquid
<div class="carousel">
  {%- for product in collection.products limit: 6 -%}
    {%- liquid
      # The first slide is visible on load, so it stays eager and is the LCP candidate.
      assign slide_loading = 'lazy'
      assign slide_priority = 'auto'
      if forloop.first
        assign slide_loading = 'eager'
        unless section.index > 1
          assign slide_priority = 'high'
        endunless
      endif
    -%}
    <div class="carousel-slide">
      {{ product.featured_image
        | image_url: width: 800
        | image_tag:
            widths: '400, 600, 800',
            sizes: '(min-width: 1000px) 33vw, 80vw',
            loading: slide_loading,
            fetchpriority: slide_priority
      }}
    </div>
  {%- endfor -%}
</div>
```

Every slide except the first gets `loading: 'lazy'`. The first slide is visible as soon as the carousel renders, so lazy-loading it delays the download until after the browser runs an `IntersectionObserver` callback. When the carousel is the first section on the page, that slide is usually the LCP element, so it also gets `fetchpriority: 'high'`. Refer to [Never lazy-load the LCP image](https://shopify.dev/docs/storefronts/themes/best-practices/performance/never-lazy-load-lcp-image).

***

## Testing

* Check the [Chrome DevTools Network panel](https://developer.chrome.com/docs/devtools/network) to verify that the correct image sizes download for different viewports.
* Use the Responsive Image Linter Chrome extension to validate `sizes` attributes.
* Compare LCP time with and without `preload: true` using the **Insights** tab in the **Performance** panel to see the LCP breakdown.
* Validate Early Hints with the `?tracing=1` parameter.

***

## References

* [`image_tag`](https://shopify.dev/docs/api/liquid/filters/image_tag) filter
* [`image_url`](https://shopify.dev/docs/api/liquid/filters/image_url) filter
* [Early learnings for Early Hints at Shopify](https://performance.shopify.com/blogs/blog/early-learnings-for-early-hints-at-shopify)
* [Responsive images on Shopify with Liquid](https://performance.shopify.com/blogs/blog/responsive-images-on-shopify-with-liquid)
* [Use responsive images](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-responsive-images)
* [Load above-the-fold and below-the-fold sections differently with `section.index`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-section-index)
* [Never lazy-load the LCP image](https://shopify.dev/docs/storefronts/themes/best-practices/performance/never-lazy-load-lcp-image)
* [Mark the LCP image with `fetchpriority="high"`](https://shopify.dev/docs/storefronts/themes/best-practices/performance/set-fetchpriority-high-on-lcp-image)

***
