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.
