Updated img_url helper: convert image formats automatically

In Ghost 5.7.0, we updated the functionality of the img_url helper: it now can convert image formats automatically! This update is perfect for theme developers who are serious about performance :racing_car:

Why it’s important

Images are the heaviest part of your website. The new helper automatically converts an image from PNG, GIF, or JPEG to WebP, which reduces its size by ~25% without any visible loss of quality. Using the new helper will make your website faster, positively impact SEO, and give your users a better experience.

How it works

Simply add the format attribute to the img_url helper.

Basic example

{{img_url feature_image size="s" format="webp"}}

Full example with picture tag

<picture>
    <!-- Serve the AVIF format if the browser supports it -->
    <!-- Remove this block when using animated images as feature images -->
    <source 
        srcset="{{img_url feature_image size="s" format="avif"}} 300w,
                {{img_url feature_image size="m" format="avif"}} 600w,
                {{img_url feature_image size="l" format="avif"}} 1000w,
                {{img_url feature_image size="xl" format="avif"}} 2000w"
        sizes="(min-width: 1400px) 1400px, 92vw" 
        type="image/avif"
    >
    <!-- Serve the WebP format if the browser supports it -->
    <source 
        srcset="{{img_url feature_image size="s" format="webp"}} 300w,
                {{img_url feature_image size="m" format="webp"}} 600w,
                {{img_url feature_image size="l" format="webp"}} 1000w,
                {{img_url feature_image size="xl" format="webp"}} 2000w"
        sizes="(min-width: 1400px) 1400px, 92vw" 
        type="image/webp"
    >
    <!-- Serve original file format as a fallback -->
    <img
        srcset="{{img_url feature_image size="s"}} 300w,
                {{img_url feature_image size="m"}} 600w,
                {{img_url feature_image size="l"}} 1000w,
                {{img_url feature_image size="xl"}} 2000w"
        sizes="(min-width: 1400px) 1400px, 92vw"
        src="{{img_url feature_image size="xl"}}"
        alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
    >
</picture>

For more details on how to use the updated helper, see the docs:

10 Likes

Amazing! I was bored of converting images manually myself.

Is this already included in the Headline theme?

@RyanF so glad to see native support for webp - has this automatic file conversion rolled out to the Headline theme?

How to implement this feature to Headline theme?

1 Like

Thank you @RyanF ,

In addition, if certain themes do not yet support the WebP format, website owners have the option to utilize a simple online tool for converting their images to WebP by accessing the following application:

We have the option to upload multiple JPEG/PNG images simultaneously and convert them all into the WebP format.

This serves as a more convenient alternative for individuals who prefer not to utilize the command line interface offered by Google’s tool - Getting Started  |  WebP  |  Google for Developers

We can also adjust the quality and resize them on the fly.

Please note that I am not affiliated, associated, or linked in any way with the above tool. Any information or opinions I may have shared regarding this tool are purely based on my personal experience or understanding, and should not be considered as an official endorsement or representation.

I would like to know too! From what I can see on Ghost Explore many publications are using Headline theme (dare I say more than Casper!)

For Headline theme, you need to make changes where the feature image is used.

One of the hbs file is feature-image.hbs which will load the image for posts/pages.
Other ones are:
loop-grid.hbs, custom-full-feature-image.hbs, tag.hbs

Considering feature-image.hbs, you can check the default configuration, which is withing the <figure class="gh-article-image"> ... </figure> tag.

You need to change this to:

<figure class="gh-article-image">
      <picture>
          <source srcset="{{img_url feature_image size="xxs" format="avif"}} 50w,
              {{img_url feature_image size="xs" format="avif"}} 100w,
              {{img_url feature_image size="s" format="avif"}} 300w,
              {{img_url feature_image size="m" format="avif"}} 500w,
              {{img_url feature_image size="l" format="avif"}} 700w,
              {{img_url feature_image size="xl" format="avif"}} 1000w,
              {{img_url feature_image size="xxl" format="avif"}} 2000w" 
              sizes="((max-width: 100px)) 100px,
              ((min-width: 100px) and (max-width: 500px)) 500px,
              ((min-width: 500px) and (max-width: 1000px)) 1000px,
              (min-width: 1000px) 1000px" type="image/avif">
          <source srcset="{{img_url feature_image size="xxs" format="webp"}} 50w,
              {{img_url feature_image size="xs" format="webp"}} 100w,
              {{img_url feature_image size="s" format="webp"}} 300w,
              {{img_url feature_image size="m" format="webp"}} 500w,
              {{img_url feature_image size="l" format="webp"}} 700w,
              {{img_url feature_image size="xl" format="webp"}} 1000w,
              {{img_url feature_image size="xxl" format="webp"}} 2000w" 
              sizes="((max-width: 100px)) 100px,
              ((min-width: 100px) and (max-width: 500px)) 500px,
              ((min-width: 500px) and (max-width: 1000px)) 1000px,
              (min-width: 1000px) 1000px" type="image/webp">
          <img srcset="{{img_url feature_image size="xxs"}} 50w,
              {{img_url feature_image size="xs"}} 100w,
              {{img_url feature_image size="s"}} 300w,
              {{img_url feature_image size="m"}} 500w,
              {{img_url feature_image size="l"}} 700w,
              {{img_url feature_image size="xl"}} 1000w,
              {{img_url feature_image size="xxl"}} 2000w" 
             src="{{img_url feature_image size="xl"}}" sizes="((max-width: 100px)) 100px,
              ((min-width: 100px) and (max-width: 500px)) 500px,
              ((min-width: 500px) and (max-width: 1000px)) 1000px,
              (min-width: 1000px) 1000px" 
              class="img-fluid feature-image" 
              alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}">
      </picture>
      {{#if feature_image_caption}}
          {{!-- Show caption if feature image caption is set --}}
          <figcaption>{{feature_image_caption}}</figcaption>
      {{/if}}
  </figure>

The exact size to use will depend on your configuration in package.json. For the above case, this is what is defined in package.json

"image_sizes": {
    "xxs": {
        "width": 50
    },
    "xs": {
        "width": 100
    },
    "s": {
        "width": 300
    },
    "m": {
        "width": 500
    },
    "l": {
        "width": 700
    },
    "xl": {
        "width": 1000
    },
    "xxl": {
        "width": 2000
    }
}

This configuration targets sizes from 50px to 2000px so you have more control over the exact image size to render based on available width.

You can see the difference in size and time it takes to load the image with and without conversion.

FORMAT SIZE (kB) TIME (ms) SIZE DIFF TIME DIFF
PNG 1200 57 0% 0%
WEBP 126 41 89.5% 28%
AVIF 88 34 92.67% 40.35%

PNG

WEBP

AVIF

The above test was done on localhost. Actual results on server may differ.

This difference is minute as compared to just a single image’s load time. But this will affect greatly when you have multiple images and when you are loading numerous posts in loop.

2 Likes