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.
Optional Parameters
The snippet supports:
excluded_src_sizes→ skip certain image sizessizes→ control how images behave across screen widthsloading,fetchpriority, anddecoding→ performance hintsanimated→ 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.

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.