Fixing Solo (Ghost theme)

Are all Ghost themes highly optimized? Let's find out.

An annotated website, showing problems with image widths

I had the following small interaction with John O'Nolan over on LinkedIn. (Here's the original thread.)

[Those of you who know me well know that I'm a super big Ghost fangirl, so I was thinking SQUEEE in my head really loudly as it went on.]

John wrote:

I love Dan’s in-depth posts so much. The amount of detail and care that goes into these is incredible. Also cool to see Ghost up there in the top tier of performance. A few thoughts on that:

All good platforms rendering a basic page are spitting out cached HTML/CSS. So while they may have vastly different perf at scale and under load, where other things become a bottleneck (often DB connection), a single page load is a more neutral comparison with less variance.

So what is the variance? As best I can tell: Mostly clientside JS.

All the modern platforms tend to make heavy use of client side JS libraries like React to render their UI. Discourse, which came out worst in this article, loads an entire Ember.js app to render its front end.

Meanwhile, all the platforms at the top of the list either have no JS at all, or vanishingly small amounts of it.

Ghost *just* creeps into this category, somewhat intentionally and somewhat by luck.

The intentional part: we modeled our front end after WordPress circa 2012. Server side templating (but logicless, so more performant) and absolutely zero JS required to render the front end of your site. A theme *can* sprinkle in JS as a progressive enhancement, but it’s not a requirement.

The lucky part: we actually do load a little bit of React on the front end these days, but not enough to have a meaningfully negative impact. We essentially have embedded front end widgets for managing components that can’t function (well) without JS. Particularly search, member signups/logins, and payments. These are unavoidable for creating the features and functionality that we want to deliver to users, but do add some overhead to the front end for perf.

I suspect we could lighten them up further by using something like HTMX, which might be an interesting path to explore in future.

Those of you who know me a little better know that I'm not a very good fangirl, so I replied:

I'd love to see Ghost make it easier (without self-hosting) to pick and choose what client side javascript it does load. It'd be awesome if I could be a little pickier about loading sodo-search (if I'm integrating with Algolia) or cards-min on an index page, or the full portal if the theme is replacing 90% of that functionality with custom pages.

I've done some occasional consulting for a guy who runs a Ghost site about living in a car. Lots of his traffic is mobile, and often in places with weak cellular. (People in cars in parks...) He gets dinged by Google for slow page loads. It'd be awesome if Ghost offered an official theme that was aggressively optimized for low-bandwidth visitors.

[Yes, I'm thinking about inability to customize {{ghost_head}} here, having spent part of week writing code for a client that replaces it..]

John's reply (OMG John replied to me!!! SQUEE!):

Agreed on the script control :)

We do have a minimal performance-specific theme: https://github.com/tryghost/zap But all our official themes are also heavily optimised, far beyond any perf penalties from Google. I can't speak to 3rd party themes though

My reply:

I'll have to take a look at Zap, next time I need something like this. (So many interesting repos in the @TryGhost organization.)

Official themes have gotten better – far more srcsets than two years ago – but there's still some room for improvement. The PageSpeed report below is for Solo (on Ghost Pro). It's an internal page with just the featured image - nothing complicated going on.

(It's at 97-99 on desktop, so that's awesome!)

Going back to our mutual client, the disabled vet whose readership is mostly on phones in their cars. His Ghost Pro plan doesn't let him load a custom theme, and he can't afford the plan upgrade. Solo works great for his content, but Google's got most of his internal pages as "need improvement", which means he's getting his search results downgraded, which means less traffic, which means less affiliate income. He can't load Zap, nor a fixed version of this theme.

Interested in a PR to fix part of the issue, which is a missing srcset on the logo image?

Fixing Solo

That was Friday, and John may have better work/life balance than I do, so he hasn't responded. But I do a lot of my Ghost work on weekends, so I found myself wondering last night: How hard would it be to fix Solo?

Solo's major PageSpeed deficits (beyond the obligatory JavaScript loading) have to do with srcsets. Or rather, lack thereof. A srcset ("source set") tells the browser that there are multiple sizes available for a particular image, and gives it some rules for which one to use where. Correct srcsets stop browsers from loading huge images, while keeping huge images available for the occasional user on the 4K display.

Here's the demo site I'm using. Note that I behaved as much like a 'real' user as possible. I didn't resize any images, just dropped them into Ghost, and figured it would take care of things.

Solo has several possible layouts. The one below is the one I see in most common use, with side by side hero and then classic feed. You can see it live at https://solo-vanilla-demo.pikapod.net/

Looks great, so what's wrong? This is what's wrong:

The logo loads at full size, whish was somewhat north of 2000px for the image I was using. The hero loads at full size. [Yes, even on phones.] And the featured images load at 1200px, although the spot they're going into is 600px even on my desktop. The first two problems are total lack of a srcset attached to those images, while the third reflects an attempt to use one srcset for both the parallax layout that spreads the image across the whole page and the classic layout in which the image will be less than 50% of the page width on desktop.

These problems have the expected effects: Loading enormous images slows down the page load.

Results are from https://www.browserstack.com/speedlab . I was initially get flummoxed by really variable run-times, so having five runs like this was helpful for seeing the performance.

It's not terrible, but it's not great, either. And this is without loading Stripe, or any analytics, or other much-wanted functionality that slows load times down.

So, I added the missing two srcsets, and tweaked the one for featured images to be different for the different layouts.

My 'speedy' version of Solo. Note the big gain in LCP.
🤔
Aside: My first run on SpeedLab is clearly anomalous for both sites. I was seeing similar issues with PageSpeed (except without any good way to show multiple runs in quick succession), which is why I switched tools. Given a somewhat persistent pattern, I think we're looking at differences in CDN/edge caching, or perhaps PikaPods does something to slow down sites not getting traffic? Or maybe we're just looking at random internet hiccups? Anyway, this is why we run 5x and pick the median, rather than just running once or taking the mean.

I wanted to do mobile testing also with SpeedLab, but it kept freezing up. Possibly because I'm on a free plan?

You'll have to take my word for it that mobile performance is better because we're loading smaller images. Or if you have better tools for testing, feel free to try out both sites and send me the results!

I haven't entirely perfected the handling of the logo. The challenge is that the logo is constrained by height, not by width. A user might upload a really wide, really short logo. If you're tweaking the theme for your own site, you probably want to tweak your site to load just the right size for the logo, instead of my compromise.

If you'd like to patch your version of Solo, here's the PR I submitted:

Improve srcsets and add where missing by cathysarisky · Pull Request #9 · TryGhost/Solo
Hello! Offering the following improvements to Solo image handling: Logo: Previously no srcset, loaded full size logo (which could be /very/ big. The challenge is that we don’t have the dimension…

More room to improve?

Well, yes, actually, although only a teeny bit. I further improved my handling of these images by setting format="webp". You can see it live at https://solo-speedy-demo.pikapod.net/

And then I reran.

Summary:

Images for 'vanilla' Solo's homepage (four posts shown plus logo and hero): 795KB

Images for 'speedy' Solo's homepage (same content, no webp): 362KB

Images for 'extra speedy' Solo's homepage (add webp): 141.4 KB

Prefer looking at PageSpeed instead of SpeedLab? Sure, let's do it.

Above: Original Solo. Below: Modded Solo. Yeah, variability is ridiculous, but there's a trend here. I think.

I'm on Ghost Pro's starter plan. What can I do?

I've submitted a PR to the Ghost team. Hopefully they'll improve Solo, and then you'll be able to load the new version just by choosing Solo from the menu and clicking 'Update'. Meanwhile, You should check the size of your logo. If it's bigger than about 48px high (or if you've adjusted that size with code injection, whatever you adjusted it to), you should resize it to be exactly high, and upload that new version. That'll stop Ghost from serving a too-big image. You should also resize important images you use regularly. You should resize your hero image to 720px wide if using the side-by-side layout since it loads every time someone visits your homepage. Going forward, consider making any new posts with featured images that are 720px wide and webp format. You'll have to decide for yourself whether there's enough of a gain in resizing/reformatting old images.

Ghost holding a stopwatch.
C'mon, c'mon, speed it up

That's all for today! Keep on loading!

Hey, before you go... If your finances allow you to keep this tea-drinking ghost and the freelancer behind her supplied with our hot beverage of choice, we'd both appreciate it!

Postscript: How can I tell if my theme has issues?

If you're looking at a theme without a srcset when you inspect it, that's a good sign that there's room for improvement. The only exception would be images that are the same size on any device, like for example a button icon. Anything else without a srcset is at least suspicious!

Here's an example of a logo without a srcset:

// Original handlebars code (in default.hbs):
<img src="{{@site.logo}}" alt="{{@site.title}}">

// Output to the browser:
<img src="https://solo-vanilla-demo.pikapod.net/content/images/2024/03/trick-or-treat-sign.png" alt="Cathys local demo site">

This HTML causes the browser to load the full-sized image.

And here's the same logo with a srcset added:

// Original handlebars code (in default.hbs):
<img 
    srcset="{{img_url @site.logo size="xs" format="webp"}} 150w, 
      {{img_url @site.logo size="s" format="webp"}} 300w"
    sizes="50vw"
    src="{{img_url @site.logo size="s" format="webp"}}" 
    alt="{{@site.title}}"
>

// Output to the browser:
<img srcset="/content/images/size/w150/format/webp/2024/03/trick-or-treat-sign.png 150w, /content/images/size/w300/format/webp/2024/03/trick-or-treat-sign.png 300w" sizes="50vw" src="/content/images/size/w300/format/webp/2024/03/trick-or-treat-sign.png" alt="Cathys local demo site">

Notice that we're giving the browser two different sizes, and they're both small!

P.P.S

Here's the .zip file if you want to install my updates without messing with Github.