Why I moved from Ghost to Next.js

October 12th 2018 - Cory Finger

Why I moved from Ghost to Next.js

Recently, I’ve adjusted my professional goals to include more artistic endeavors. I’ve left the field of Artificial Intelligence to give me more time to pursue my passions of figure drawing, environmental art and application design.

I’ve also started focusing on frontend development more intensely than at any other point in my career and have been having an amazing time with it.

When I started this blog, I intended to do minimal modifications to the programming behind it and focus entirely on the writing aspect. I figured that writing was the most important part and everything else was bells and whistles on top of it.

Ghost is amazing for writing. It’s truly a plug and play solution for starting a good-looking, low-maintenance blog!

Shift in Thinking

As I started studying design, the concepts of readability, accessibility, and responsiveness got my mind racing. I wanted PragmaCoders to do everything a website could do to reach more people and bring joy to readers.

I started implementing each of these ideas. I tweaked the design to be responsive, minimize distraction and lead the reader on a more optimal path through articles.

I started writing hacked-together JavaScript to get around the limited variables that could be passed to the frontend from the backend editor. Stretching the existing framework to its limits.

The inability to have complex plugins isn’t really a design flaw of the Ghost blogging platform. It’s just not something it was built to allow the user to do. Wordpress has insane amounts of complicated customization. Ghost has sane defaults and elegant simplicity.

Breaking Down

I continued this way for a long time.

I began putting custom HTML in my posts to encourage readers to read relevant articles. This eliminated a main benefit of markdown - transportability. Custom HTML would break if I later changed the design or moved platforms.

Then I wanted to optimize images to use the srcset attribute. I wanted to upload the images once and them be duplicated and resized to several mobile friendly resolutions to save readers bandwidth and time.

I ended up coming up with a solution where I built an NGINX proxy with a module that resized images on the fly and cached them in memory. I proceeded to make a series of JS scripts that’d automatically replace the images Ghost produced with URLs to that proxy. It was all very clever and also a really convoluted way to solve a foundational problem with what I was doing.

It’s fair to say that my plan to focus on writing good articles had taken a detour. Also - the cost to host all this was more than I was comfortable with.

I needed to change my approach.

Desire to Change

I’ve been working with React for a number of years now and knew that this setup was more complicated than it needed to be.

My blog didn’t need users or even a database - the writers included me and sometimes a guest. Posts don’t change that often.

Markdown is a pretty standard format. I could easily use a library to convert from Markdown to HTML in a couple lines of code.

I design in HTML and CSS. JSX is great for those and Webpack could make them even more powerful and easy to use.

The image optimization problem - one Webpack script away from being off my mind.

After all my trouble, the switch to React seemed like an easy decision.

The Problem

React is rendered a while after the page loads.

While search engines like Google are making changes to support this - discoverability is not something that should be taken for granted. I don’t want the search engines to have to work hard and possibly miss something important.

Proper HTML is also super useful for screen readers and other things that help people to read and understand the content of the page.

Switching to a JS only website would go the opposite direction of my accessibility goals.

There was a secondary issue where I needed to learn how to implement good SEO on my own - without the help of Ghost. But I was able to do that with a combination of research and analyzing what Ghost does to make things SEO friendly.

The Solution

Server side rendering with Next.js!

Next.js can take a bunch of my React components and compile them to HTML. It can then rehydrate that HTML, after it is loaded - to allow for dynamic interaction (if desired).

I can now write my blog posts in Markdown, compile them to HTML, stick that in a React template and compile the whole thing to a set of optimized HTML files that support non-JS enabled browsers.

I can then throw those HTML files into S3 and point Cloudfront at them. Good to go!

I use a Webpack loader to resize my images into several different sizes to optimize for mobile bandwidth and still look good at every size. I can further use a React component to lazy load these images - delaying download until the user has scrolled to them.

As a bonus, the blog now costs pennies a month to host and I can optimize serving to traffic all around the world with the Cloudfront CDN.


I didn’t exactly stick to my goal of minimal programming. But - this IS a programming blog and I can now use React to create interactive tutorials and help people to learn new concepts in an easier way.

This way, I won’t have to compromise on the accessibility or form of the content I produce. I can use my programming to enable my artistic pursuits and both pursuits can be more interesting for it.

So far, I think I made a good choice in switching to Next.js!

All that being said - I would not recommend this to someone unless they are comfortable with React and JS transpilation via Webpack. If not, it’d be a bit complex of an undertaking to jump into.

Here's a look at what my posts look like now:

import { SnowplowTutorials } from '../../tags'
import { Cory } from '../../authors'
import showdown from 'showdown'
import post from './post.md'

export default {
  title: 'Ghost to Next.js',
  description: 'The reasons behind moving the ...',
  header: require('./header.png?min=800,max=1440,steps=6'),
  thumb: require('./header.png?min=100,max=440,steps=3'),
  published_time: currDateTime.toISOString(),
  modified_time: currDateTime.toISOString(),
  tags: [],
  author: Cory

const converter = new showdown.Converter()
const html = converter.makeHtml(post)

export const Component = () => (
  <div dangerouslySetInnerHTML={{__html: html}} />

Looking forward

I’m excited for this new chapter of PragmaCoders!