Image Management in Ghost - A performance report

Since its first release, Ghost - an open source publishing platform - has come a long way. It has immediately gained popularity because it’s easy of use and simplicity. Since then, lot of new features have been added to it, and the platform became a lot more robust: it's very easy to manage it via it's CLI, it's easy to create customisations using its amazing API, and there's a myriad of new features that are added in each release and that are being actively worked on.

Being one of the earlier adopters of Ghost I have immediately fallen in love with it and have used it to launch a few blogs of my own - including this very blog that you're reading at the moment.

There's one feature of this otherwise great platform missing in my honest opinion. Image management. Don't get me wrong, Ghost can handle images but it's rather basic, and it's not feasible if we want to keep a blog that performs well (i.e., loads fast). And if you don’t take my word for it, you can see a lot of discussion on this topic on the Ghost Forum.

Image management in Ghost

So what does Ghost do at the moment regarding image management? Using the new Koenig editor (before it was only Markdown) we can add images as well as image galleries to blog posts (plus a great number of other things, please refer to the link before) and the upload of the image assets are also handled by Ghost. The images get uploaded to the server and served directly from the filesystem. The code behind the displaying implementation 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 is used in conjunction with the img element to display the image, and by default, the upload folder is the root of the Ghost blog followed by /content/images/ and a year and month folder.

As mentioned before using such image management is easy, and very straightforward and at least using this new editor some basic operations on images like uploading is handled.

Concerns regarding the current solution

However, there are a few concerns regarding this approach, and there's a lot more that could be done. There are a lot of companies that have started to use Ghost as their publishing platform and some heavily use media such as images - engineering blogs, including Mozilla’s VR Blog, Tinder and DuckGoGo all use Ghost. (You can take a look at some of Ghost's Customers.)

Let's walk through an example and see an interesting issue with Ghost's current image management solution. The basic premise here is that we'd like to avoid having to edit an image before uploading it to Ghost and using it in a blog post - some people may not have the necessary skills (Photoshop) anyway to do exhaustive image manipulation.

The test setup

To run a few tests, I had setup Ghost locally and added two articles with the same content. Each article has a cover image, two large photos displayed in the manner discussed earlier plus an image gallery was also added to the article.

A typical use case

Let's consider a travel blogger or even a travel magazine wishing to use Ghost. Such a blog is by default going to be very visual, sharing a lot of images were snapped during the travel and therefore creating a visual experience that would, in turn, get more people visiting the site. A very simple idea which should be created using Ghost very easily.

Now, of course, this is only a sample blog article, and there's not much text, but there are nine images displayed on the page. We have to assume that our blogger in question does what the majority of people would do: take photos on a mobile device and use the Ghost editor to add them to the article. This has quite a few bad repercussions:

  • when displayed, the image will be scaled down using CSS only
  • the picture is not going to consider the browser and always be presented in a given format (e.g. JPG)

Even before running any tests we can use the Chrome DevTools to check that the images are scaled down either via CSS or by the browser itself:

What happens is that we load an image that's 2000 x 2667 pixels into a space that can hold only 840 x 1120 pixels. This also means that we are downloading the entire image which is about 615 KB in size. That again is not the best thing to do. In fact, the size of the image served is the same size on the filesystem after the upload:

Note that the Koenig editor in Ghost can display the image in its original size or resize it as we saw it in the example. The raw image is 2.5 MB in size. We are using a scaled version, but that's still not optimal.

Please also note that if, on the same day the same image is uploaded Ghost will keep a copy of both pictures taking up disk space in the same folder. (Ghost appends the filename with -[x] where [x] is an integer). In fact, the same image could be uploaded every day and be placed in the appropriate folder creating massive redundancy.

Let's run the site through a Website Speed Test tool that will take a look at all the media on the site and offer some recommendations.

We can see from the above that we got a "Mediocre" C score, which is not great. 11 images got analysed on our page, and just these 11 images contribute to 6MB of the site. That's quite a lot considering we "only" have 11 images.

Let's take a look at an optimised version of the site as well as discuss how the was optimisation done.

Each image has been uploaded in its raw format to Cloudinary, and it's being served from there via URLs that also make the following changes:

  • reduce the quality of the image without impacting the visuals to the human eye
  • serve the right type of file per each browser (e.g. WebP for Chrome)
  • apply the right width to the images

These optimisations performed are available from Cloudinary out of the box, and we only need to manipulate the image URL. So this is what we had before:

<img src="/content/images/2018/10/PPFUMdBlS3ahDoquOdamkQ.jpg" class="kg-image">

And we merely turned this into:

<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?

The same number of images that now only contribute to 1MB of the weight of the blog.

Please note that the optimised page was created entirely manually.

Clearly there's a need to handle images online - especially in blogs which could display a lot of visual content. Having a third party solution integrated would help achieve this goal. Cloudinary has a plugin for WordPress to manage image galleries - such a plugin would make a great addition for Ghost as well.

DPR, Responsive Breakpoints

We could go fancier and change the DPR of the image as well as add responsive breakpoints. To do this, we can use the dpr_auto flag, and Cloudinary has a Responsive Breakpoint Generator to further aid us in adding such breakpoints.

To read more about DPR and Responsive Breakpoints, please read Responsive Images with 'srcset' and 'sizes'

Watermarks, because, we can

While creating this article, I knew I wanted to concentrate on performance comparison between the Ghost "native" image solution vs Cloudinary, but it occurred to me that a lot of bloggers like to place a copyright message or a logo as a watermark to images. This couldn't be easier by using Cloudinary:

<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:

Here's the image that we have seen previously and notice that now in the bottom right corner the Cloudinary logo appears (any logo can be used as an overlay watermark) - all this achieved again just by manipulating the URL. Imagine if this would be a core feature added to Ghost!

Feature request

There's an open feature request for Ghost to have an improved Media Library added to the project - take a look at it, upvote it and add your thoughts.

Plugins

There are a bunch of plugins that integrate Ghost with Cloudinary already, and you can find them in the official Ghost Documentation.

Conclusion

Ghost, as a blogging engine has been my favourite for the past many years and it's packed with great features however as noted in this article there's a lack for image management and there's a lot more that could be done regarding image delivery and manipulation. Such addition(s) - should those be created by the Core team or as an open-source contribution - would not only be convenient but it could help immensely with site performance and using a tool such as Cloudinary would come with extra benefits on top of image management - such as adding watermarks to images.