Optimize Ghost Blog Performance Including Rewriting Image Domains to a CDN
Tags: webperf, webdev, devops
Published: 2020-09-10 18:23:33

The Ghost blogging platform offers a lean and minimalist experience. And that's why we love it. But unfortunately sometimes, it can be too lean for our requirements. 

Web performance has become more important and relevant than ever, especially since Google started including it as a parameter in its SEO rankings. We make sure to optimize our websites as much as possible, offering the best possible user experience. This article will walk you through the steps you can take to optimize a Ghost Blog's performance while keeping it lean and resourceful. 

When we started working on the [appfleet blog](https://appfleet.com/blog) we began with a few simple things:

###Ghost responsive images###

The featured image in a blog have lots of parameters, which is a good thing. For example, you can set multiple sizes in `package.json` and have Ghost automatically resize them for a responsive experience for users on mobile devices or smaller screens.

```
"config": {
		"posts_per_page": 10,
		"image_sizes": {
			"xxs": {
				"width": 30
			},
			"xs": {
				"width": 100
			},
			"s": {
				"width": 300
			},
			"m": {
				"width": 600
			},
			"l": {
				"width": 900
			},
			"xl": {
				"width": 1200
			}
                 }
}
```

And then, all you have to do is update the theme's code

```
<img class="feature-image"
    srcset="{{img_url feature_image size="s"}} 300w,
            {{img_url feature_image size="m"}} 600w,
            {{img_url feature_image size="l"}} 900w,
            {{img_url feature_image size="xl"}} 1200w"
    sizes="800px"
    src="{{img_url feature_image size="l"}}"
    alt="{{title}}"
/>
```

###Common HTML tags for performance###

Next we take a few simple steps to optimize *Asset Download Time*. That includes adding `preconnect` and `preload` headers in `default.hbs`:

```
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin="anonymous">
<link rel="preconnect" href="https://cdn.jsdelivr.net/" crossorigin="anonymous">
<link rel="preconnect" href="https://widget.appfleet.com/" crossorigin="anonymous">

<link rel="preload" as="style" href="https://fonts.googleapis.com/css?family=Red+Hat+Display:400,500,700&display=swap" />
<link rel="preload" as="style" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/css/all.min.css" />
```

As we load many files from [jsDelivr](https://www.jsdelivr.com/) to improve our performance, we instruct the browser to establish a connection with the domain as soon as possible. Same goes for Google Fonts and the sidebar widget that was custom coded.

Most often than not, users coming from Google or some other source to a specific blog post will navigate to the homepage to check what else we have written. For the same reason, on blog posts we also added `prefetch` and `prerender` tags for the main blog page.
 
That way the browser will asynchronously download and cache it, making the next most probable action of the user almost instant:

```
<link rel="prefetch" href="https://appfleet.com/blog">
<link rel="prerender" href="https://appfleet.com/blog">
```

Now these optimizations definitely helped but we still had a big problem. Our posts often have many screenshots and images in them, eventually impacting the page load time. 

To solve this problem we took two steps. Lazy load the images and use a CDN. The issue is that Ghost doesn't allow to modify or filter the contents of the post. All you can do is output the HTML.

The easiest solution to this is to use a dynamic content CDN like [Cloudflare](https://www.cloudflare.com/). A CDN will proxy the whole site, won't cache the HTML, but cache all static content like images. They also have an option to lazy load all images by injecting their own Javascript.

But we didn't want to use Cloudflare in this case. And didn't feel like injecting third-party JS to lazy load the images either. So what did we do?

###Nginx to the rescue!###

Our blog is hosted on a [DigitalOcean](https://www.digitalocean.com/) droplet created using its marketplace apps. It's basically an Ubuntu VM that comes pre-installed with Node.js, NPM, Nginx and Ghost.

> Note that even if you don't use DigitalOcean, you are still recommended to use Nginx in-front of the Node.js app of Ghost.

This eventually makes the solution pretty simple. We use Nginx to rewrite the HTML, along with enabling a CDN and lazy-loading images at the same time, without any extra JS.

For CDN, you may also use the free CDN offered by Google to all AMP projects. Not many people are aware that you can use it as a regular CDN without actually implementing AMP. 

All you have to do is use this URL in front of your images:

```
https://appfleet-com.cdn.ampproject.org/i/s/appfleet.com/
```

Replace the domains with your own and change your `<img>` tags, and you are done. All images are now served through Google's CDN.

The best part is that the images are not only served but optimized as well. Additionally, it will even serve a WebP version of the image when possible, further improving the performance of your site.

As for lazy loading, you may use the native functionality of modern browsers that looks like this `<img loading="lazy"`. By adding `loading="lazy"` to all images, you instruct the browsers to automatically lazy load them once they become visible by the user.

And now the code itself to achieve this:

```
server {
    listen 80;

    server_name NAME;

    location ^~ /blog/ {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host       "appfleet.com";
        proxy_set_header        X-Forwarded-Proto https;
        proxy_pass http://127.0.0.1:2368;
        proxy_redirect off;
		
        #disable compression 
        proxy_set_header Accept-Encoding "";
        #rewrite the html
        sub_filter_once off;
        sub_filter_types text/html;
 		sub_filter '<img src="https://appfleet.com' '<img loading="lazy" src="https://appfleet-com.cdn.ampproject.org/i/s/appfleet.com';
    }

}
```

First we disable compression between node.js and nginx. Otherwise nginx can't modify the HTML if it comes in binary form. 

Next we use the `sub_filter` parameter to rewrite the HTML. Ghost is using absolute paths in images, so we add the beginning as well. And in 1 line enabled both the CDN and lazyloading.

Reload the config and you are good to go. Check our blog to see this in real time.