Image Management in Ghost - A performance report
Older Article
This article was published 8 years ago. Some information may be outdated or no longer applicable.
Ghost has come a long way since its first release. The open source publishing platform grabbed attention fast because it’s simple and easy to pick up. Since then, the team’s bolted on a CLI for management, a solid API for customisation, and a steady stream of new features with every release.
I was an early adopter. Fell in love with it, spun up a few blogs (including the one you’re reading right now).
But there’s one thing missing from this otherwise brilliant platform. Image management. Ghost can handle images, sure, but it’s basic. And it won’t cut it if you want a blog that actually loads fast. You don’t have to take my word for it. There’s plenty of discussion on the Ghost Forum.
Image management in Ghost
So what does Ghost actually do with images right now? The Koenig editor (replacing the old Markdown-only editor) lets you drop in images, image galleries, and a bunch of other content types. Ghost handles the upload itself, storing assets on the server and serving them straight from the filesystem. The markup looks like this:
<figure class="kg-card kg-image-card">
<img src="/content/images/2018/10/IMG_1234.jpg" class="kg-image" />
<figcaption>Some caption</figcaption>
</figure>
The `figure` element wraps the `img` element for display. By default, uploads land in the Ghost blog's root under `/content/images/` in a year-and-month folder. Basic operations like uploading work fine with the new editor.
### Concerns regarding the current solution
There are a few problems with this approach, though, and a lot of room for improvement. Plenty of companies have built their publishing operations on Ghost, many of them image-heavy: engineering blogs, Mozilla's VR Blog, Tinder, DuckDuckGo. (Check out [Ghost's Customers](https://ghost.org/customers/) for more.)
Let's walk through an example. The premise: we want to avoid editing images before uploading them to Ghost. Some people don't have the skills (or Photoshop) for heavy image manipulation anyway.
# The test setup
I set up Ghost locally and created two articles with identical content. Each article got a cover image, two large inline photos, and an image gallery.
## A typical use case
Picture a travel blogger or a travel magazine running Ghost. That kind of blog lives and dies on visuals. Lots of photos snapped during trips, creating an experience that pulls readers in.

This is just a sample article without much text, but nine images sit on the page. Our blogger does what most people do: snaps photos on a phone and drops them into the Ghost editor. That creates a few problems:
- The displayed image gets scaled down with CSS only
- The picture ignores the browser and always ships in whatever format it was taken in (e.g. JPG)
Before running any tests, Chrome DevTools shows us the images are being scaled down by CSS or the browser:

We're loading a 2000 x 2667 pixel image into a space that fits 840 x 1120 pixels. That means downloading the full image at roughly 615 KB. The served file is the same size as what's sitting on the filesystem:

> Note that the Koenig editor can display images at original size or resize them as shown. The raw image weighs 2.5 MB. We're using a scaled version, but it's still not great.
> Also worth noting: if you upload the same image on the same day, Ghost keeps a copy of both, appending the filename with -[x]. Upload that image every day and you'll create massive redundancy across folders.
Let's run the site through a [Website Speed Test](https://webspeedtest.cloudinary.com) tool.

A "Mediocre" C score. Not great. 11 images got analysed, and they contribute 6 MB to the page weight. That's a lot for 11 images.
Let's look at an optimised version. Each image was uploaded in its raw format to Cloudinary and served from there via URLs that apply three changes:
* Reduce quality without visible impact to the human eye
* Serve the right file type per browser (e.g. WebP for Chrome)
* Apply correct dimensions
These optimisations come out of the box with Cloudinary. You just tweak the URL. So this:
```html
<img
src="/content/images/2018/10/PPFUMdBlS3ahDoquOdamkQ.jpg"
class="kg-image"
/>
Becomes this:
<img
src="https://res.cloudinary.com/tamas-demo/image/upload/f_auto,q_auto,w_840,h_1120/ghost/PPFUMdBlS3ahDoquOdamkQ.jpg"
class="kg-image"
/>
Where:
- f_auto delivers the right image format to the browser (WebP for Chrome, JPEG2000 for Edge etc.)
- q_auto reduced the quality of the image without a visual impact
- w_840 and h_1120 resize the image to be 840 x 1120 pixels
All these options above help us reduce the size of the image.
And the result?

Same number of images. Now they contribute just 1 MB to the page weight.
The optimised page was created entirely by hand.
There’s a real need for proper image handling in blogs, especially visual ones. A third-party integration would solve this. Cloudinary already has a plugin for WordPress to manage image galleries. Something similar would be a brilliant addition to Ghost.
DPR, Responsive Breakpoints
We could go further and adjust the DPR of the image, plus add responsive breakpoints. The dpr_auto flag handles DPR, and Cloudinary’s Responsive Breakpoint Generator helps with breakpoints.
To read more about DPR and Responsive Breakpoints, please read Responsive Images with ‘srcset’ and ‘sizes’
Watermarks, because, we can
While putting this article together, I wanted to focus on performance. But it hit me that a lot of bloggers like slapping a copyright message or logo onto their images. With Cloudinary, that’s trivial:
<img
src="https://res.cloudinary.com/tamas-demo/image/upload/f_auto,q_auto,w_840,h_1120/w_80,g_south_east,x_5,y_5,l_logo/ghost/PPFUMdBlS3ahDoquOdamkQ.jpg"
/>
Where:
- After
/upload/we find the previously mentioned options - After these options we get another set of content between
//- think of this as a layer - l_logo is a layer referencing an asset called ‘logo’ (already uploaded in a Cloudinary account
- w_80 is the width of the logo
- g_south_east is the location of the layer
- x_5, y_5 indicates the location of the logo: 5 pixels away from the south east corner of the main image
And the result looks like this:

The same image from before, but now the Cloudinary logo sits in the bottom right corner (any logo works as an overlay watermark). All done by tweaking the URL.
Feature request
There’s an open feature request for an improved Media Library in Ghost. Go upvote it and drop your thoughts.
Plugins
A handful of plugins already integrate Ghost with Cloudinary. You’ll find them in the official Ghost Documentation.
Conclusion
Ghost has been my favourite blogging engine for years, and it’s packed with great features. But image management is a blind spot. There’s a lot more that could be done around image delivery and manipulation. Whether the Core team builds it or it comes as an open-source contribution, proper image handling would boost site performance considerably. And a tool like Cloudinary would bring extra benefits on top, like the watermarking we just saw.