When I started customizing my Ghost theme, I quickly realized how repetitive image handling could be. Writing <img> tags manually for every post wasn’t scalable — especially if I wanted AVIF/WebP, responsive sizes, and lazy loading.

So, I decided to build a reusable image partial in Ghost using a Handlebars partial. Here’s how it works and how you can adapt it to your own theme.


The Concept

Ghost themes use Handlebars (.hbs) templates. You can define reusable components as partials, then include them anywhere with:

{{> image src=feature_image alt=feature_image_alt class="rounded-xl"}}

This avoids repetition and ensures consistency in your theme design.


The Code Explained

Here’s the reusable image component (partials/image.hbs):

{{!--
Reusable image template for Ghost themes
Params:
  - src (required)
  - alt (optional)
  - class (optional)
  - loading (optional)
  - sizes (optional)
  - default_src_size (optional, defaults to "xl")
  - width / height (optional)
  - decoding (optional)
  - fetchpriority (optional)
  - draggable (optional)
  - animated (optional)
  - excluded_src_sizes (optional, bracket-separated list: e.g. "[xxs][xs]")
Example usage:
{{> image
    src=feature_image
    alt=feature_image_alt
    class="rounded-xl"
    loading="lazy"
    sizes="(max-width: 768px) 100vw, 768px"
    excluded_src_sizes="[xxs][xs]"
}}
--}}

{{#if src}}
  <picture{{#if class}} class="{{class}}"{{/if}}>
      {{#unless animated}}
      <source
          srcset="
              {{^match excluded_src_sizes "~" "[xxs]"}}{{img_url src size="xxs" format="avif"}} 30w,{{/match}}
              {{^match excluded_src_sizes "~" "[xs]"}}{{img_url src size="xs" format="avif"}} 150w,{{/match}}
              {{^match excluded_src_sizes "~" "[s]"}}{{img_url src size="s" format="avif"}} 300w,{{/match}}
              {{^match excluded_src_sizes "~" "[m]"}}{{img_url src size="m" format="avif"}} 720w,{{/match}}
              {{^match excluded_src_sizes "~" "[l]"}}{{img_url src size="l" format="avif"}} 960w,{{/match}}
              {{^match excluded_src_sizes "~" "[xl]"}}{{img_url src size="xl" format="avif"}} 1200w,{{/match}}
              {{^match excluded_src_sizes "~" "[xxl]"}}{{img_url src size="xxl" format="avif"}} 2000w,{{/match}}
              {{img_url src}}"
          {{#if sizes}}sizes="{{sizes}}"{{/if}}
          type="image/avif">
      {{/unless}}

      <source
          srcset="
              {{^match excluded_src_sizes "~" "[xxs]"}}{{img_url src size="xxs" format="webp"}} 30w,{{/match}}
              {{^match excluded_src_sizes "~" "[xs]"}}{{img_url src size="xs" format="webp"}} 150w,{{/match}}
              {{^match excluded_src_sizes "~" "[s]"}}{{img_url src size="s" format="webp"}} 300w,{{/match}}
              {{^match excluded_src_sizes "~" "[m]"}}{{img_url src size="m" format="webp"}} 720w,{{/match}}
              {{^match excluded_src_sizes "~" "[l]"}}{{img_url src size="l" format="webp"}} 960w,{{/match}}
              {{^match excluded_src_sizes "~" "[xl]"}}{{img_url src size="xl" format="webp"}} 1200w,{{/match}}
              {{^match excluded_src_sizes "~" "[xxl]"}}{{img_url src size="xxl" format="webp"}} 2000w,{{/match}}
              {{img_url src}}"
          {{#if sizes}}sizes="{{sizes}}"{{/if}}
          type="image/webp">

      <img
          srcset="
              {{^match excluded_src_sizes "~" "[xxs]"}}{{img_url src size="xxs"}} 30w,{{/match}}
              {{^match excluded_src_sizes "~" "[xs]"}}{{img_url src size="xs"}} 150w,{{/match}}
              {{^match excluded_src_sizes "~" "[s]"}}{{img_url src size="s"}} 300w,{{/match}}
              {{^match excluded_src_sizes "~" "[m]"}}{{img_url src size="m"}} 720w,{{/match}}
              {{^match excluded_src_sizes "~" "[l]"}}{{img_url src size="l"}} 960w,{{/match}}
              {{^match excluded_src_sizes "~" "[xl]"}}{{img_url src size="xl"}} 1200w,{{/match}}
              {{^match excluded_src_sizes "~" "[xxl]"}}{{img_url src size="xxl"}} 2000w,{{/match}}
              {{img_url src}}"
          {{#if sizes}}sizes="{{sizes}}"{{/if}}
          src="{{#if default_src_size}}{{img_url src size=default_src_size}}{{else}}{{img_url src size="xl"}}{{/if}}"
          {{#if alt}}alt="{{alt}}"{{/if}}
          {{#if class}}class="{{class}}"{{/if}}
          {{#if loading}}loading="{{loading}}"{{/if}}
          {{#if decoding}}decoding="{{decoding}}"{{/if}}
          {{#if fetchpriority}}fetchpriority="{{fetchpriority}}"{{/if}}
          {{#if draggable}}draggable="{{draggable}}"{{/if}}
          {{#if width}}width="{{width}}"{{/if}}
          {{#if height}}height="{{height}}"{{/if}}>
  </picture>
{{/if}}

It uses the <picture> tag to provide multiple formats (AVIF, WebP, fallback JPG/PNG).
Ghost automatically generates responsive image sizes, so your visitors only download what they need.

<picture>: The Picture element - HTML | MDN
The <picture> HTML element contains zero or more <source> elements and one <img> element to offer alternative versions of an image for different display/device scenarios.

Optional Parameters

The snippet supports:

  • excluded_src_sizes → skip certain image sizes
  • sizes → control how images behave across screen widths
  • loading, fetchpriority, and decoding → performance hints
  • animated → skip AVIF for GIFs

You can call it anywhere in your theme, for example inside post.hbs or card.hbs.


Why This Matters

A reusable image component:

  • Simplifies your theme code
  • Improves Lighthouse performance scores
  • Ensures every image uses best practices automatically

Once you set it up, you never have to worry about srcset again.


Personal Reflection

At first, I thought performance tweaks like this were overkill. But after seeing the difference in loading speed — especially on mobile — I realized small details add up.
Coding is like brewing coffee: take time, refine the process, and the result feels rewarding.

Reusable image template for Ghost themes
Reusable image template for Ghost themes. GitHub Gist: instantly share code, notes, and snippets.

Building a Reusable Image Partial for `Ghost` Themes

Learn how to build a reusable, optimized image partial in Ghost using Handlebars. We’ll explore how it works, how to customize it, and why it matters for performance.

Building a Reusable Image Partial for `Ghost` Themes