Ghost and Livereload.js in production

livereload.js stays in your theme if you use the vanilla GitHub action to update your Ghost website using your custom theme. Remember to compile your theme for production before uploading.

Alec Di Vito
Alec Di Vito 4 min read
Ghost and Livereload.js in production
TryGhost Github action for uploading themes

Ghost is a blogging platform that makes starting a blog easy. Write blog posts and grow your readers through one platform. Ghost can hosting this for you but they also give the user the ability to do so as well. This is the route I've decided to take.

Along with self hosting Ghost, I've also created my own theme (WIP). I want to style this blog my way and in the future I may want to change small parts of it. I like the fact that everything in Ghost is customized by handlebar files instead of React (like Gatsby). I have a lot more confidence handlebars staying around another 10 years then Gatsby staying around with all the great alternatives that exist now-a-days.

But I digress, so, like any good developer, I set off and stood up Ghost on my Raspberry pi home lab. I created a repo to house my template and created a GitHub action to upload the theme anytime there was a push to master. Ghost provides users with a helpful getting started guide and I was able to use that as a template. Within 5 minutes, I had my custom theme on my self hosted Ghost website. Awesome!!!

There was one small issue though...My website kept loading after I visited the website...

0:00
/0:09

Loading for an entire 9 seconds! Impressive

I thought it would leave at some point and so I left it. I wasn't in a rush either as I don't expect anyone to visit the site right now. So there it sat until today, after my friend visited the site and asked the question:

Why is the site still loading after like 1 second?

Ugh, well, fine, if you are going to visit the site, I guess I'll figure it out ☠️ lo and behold, the culprit! livereload.js?snipver=1

Livereload live reloading stays pending until cancelation

Hmm, now why is my production blog site trying to load this livereload.js file? I thought I was doing a production build and then uploading it to my site. I got most of my automation through their getting started guide. The action that TryGhost even provides a beautiful GitHub action for this purpose, which you can read below:

name: Deploy Theme
on:
  push:
    branches:
      - master
      - main
jobs:
  deploy:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v3
      - name: Deploy Ghost Theme
        uses: TryGhost/action-deploy-theme@v1
        with:
          api-url: ${{ secrets.GHOST_ADMIN_API_URL }}
          api-key: ${{ secrets.GHOST_ADMIN_API_KEY }}

Isn't that beautiful, they removed all the logic so that deploying a theme to a instance of Ghost is dead simple and straight forward. However, it doesn't answer the question of why livereload.js is being uploaded? Maybe some google searches will lead to an answer.

Surfing the web

First we go to the source of my theme template, TryGhost/Starter where there is an issue about this exact problem. It was recently posted 5 months ago. Sweet! But sadly it was closed. Damn, no leads.

Production deployment via GitHub Action inject Livereload.js · Issue #91 · TryGhost/Starter
When the theme get deployed via the GitHub Action in the theme, the livereload.js is injected into the production environment (despite having the value “production” for the BUILD variable on GitHub…

My second thought was that maybe there was a way to tell Ghost's GitHub action to do a production build. I'll spare the details of the javascript action because it's simple, but the script is basically packaging up the project as is, without building it, and uploading that zip file to the server where it will be published. Therefore, you would still have the livereload.js call to your production server because there is no build step.

Finally, I stumbled upon the issue documented in GitHub action repo TryGhost/action-deploy-theme where other's discussed the problem. Crazy that this is the first issue. Although it's not surprising as the repository readme doesn't have any information about the topic!

Compile assets before uploading · Issue #1 · TryGhost/action-deploy-theme
My theme needs to build assets before uploading. It would be nice to add an option to run gulp/webpack whatever or, as an alternative, accept an existing zip file instead of creating one.

The issue recommends to build around the upload action, which, in hindsight, makes sense. Using the recommendation in the issue, I copy, pasted and updated the code snippets into what I use today.

# Learn more → https://github.com/TryGhost/action-deploy-theme#getting-started
name: Deploy Theme
on:
  push:
    branches:
      - master
      - main
jobs:
  deploy:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v3

      - name: Cache node modules
        uses: actions/cache@v4.0.2
        with:
          path: node_modules
          key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.OS }}-build-${{ env.cache-name }}-
            ${{ runner.OS }}-build-
            ${{ runner.OS }}-
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4.0.3
        with:
          node-version-file: .nvmrc
      - run: npm install
      - name: Build theme
        env:
          NODE_ENV: production
        run: npm run zip

      - name: Deploy Ghost Theme
        uses: TryGhost/action-deploy-theme@v1
        with:
          api-url: ${{ secrets.GHOST_ADMIN_API_URL }}
          api-key: ${{ secrets.GHOST_ADMIN_API_KEY }}
          file: alecdivito-website-ghost-theme.zip # Note: This maybe different for you.

In Conclusion

Turns out that self hosting Ghost isn't as easy as it seems. There are some gotcha's that aren't clearly documented (or I'm not reading all of their getting started documentation haha). However, I now have a working landing page that doesn't take (technically) 8 seconds to load, and to me, thats a win!

0:00
/0:03