2040 words
~10min read

2024: A Blog Odyssey from Ghost through Hugo to Astro

October 2nd 2024

Whoohoo! My new site is launched! 🎉

As of this post I just finished remaking my site in Astro after using Hugo for the last iteration. I figured now’s a good time to post about the journey this site has taken thus far in it’s 11 year history and maybe a sneak peek of what’s in store for the future.

The first version of my site is a hand coded static single page app hosted on dillonshook.is

dillonshook.com registered

Started using Ghost as a blog platform running inside an express node server to be able to make custom APIs

Upgraded Ghost from 1.12 to 2.16 and it's a royal pain where everything seems to go wrong with the full stack

Get fed up with upgrading Ghost again, decide to use Hugo as a simple site generator. It takes 3 weeks to rebuild.

Rebuild again, this time in in Astro. It takes one week.

From the timeline it seems like fall is the time for me to work on the site 🍂

Anyways, going way back to the beginning, here was my first attempt at a fun personal site. It’s cringy to look at now but it shows I was trying to make something new and different even if the results weren’t very polished. You can also spot the beginnings of the Lets build a Guitar project in there!

On to Ghost#

The hand rolled site approach obviously wasn’t going to cut it long term for the site so I started looking for an alternative. I wanted to stay in the JS ecosystem after running a few Wordpress sites so it was a fairly obvious choice to go with Ghost which was starting to get hot around the time as well. This was a great choice at the time and got me going with a pretty good setup to tinker around with. At the time Ghost allowed you to run it off of an express server which was pretty sweet because you could then add other custom APIs to the same domain (and express router) for fun projects like a Spotify covers playlist generator I wrote that has since been lost to time.

Fast forward a few years down the line and I figure it’s time to upgrade Ghost from the 1.x version I was on to the 2.x version that promised some fancy features. Things really went off the rails in this upgrade with so many different things breaking as part of it. My stream of thought notes from the time:

  • docker cloud not working because of bad dependency on ubuntu server
  • haproxy and nginx proxy not working with setup and docker networks
  • ghost markdown now requires extra line breaks between html tags inserting paragraphs breaking slideshow integration
  • ghost upgrade changes enough html structure and classes to break css theme
  • js and sass gulp builds now break because of outdated dependencies
  • endless messing around with old ghost docker image trying to mount data and config into different places with it never ending up in the right spot and bringing up the content
  • NVM on windows breaking npm install with path too long
  • endless time messing around with upgrading the ghost version incrementally, with different versions of node and npm to try to satisfy sqlite build failure
  • error starting in docker container: Error starting userland proxy: mkdir /port/tcp:0.0.0.0:2368:tcp:172.19.0.2:2368: input/output error
  • ghost 2.0 upgrade breaks compatibility, doesn’t support running from express server

A lot of these can be blamed on the immature Docker and JS ecosystems at the time but it still was a testament to how important it is to limit your dependencies. As the last bullet pointed out as well the 2.0 release got rid of the feature to run it inside of your own express server which meant I had to take down my Spotify playlist generator.

There are certainly easier ways to host a site (like having someone else do it for you) but there are some really important goals for my site:

  • Self Hosted
  • Git version everything including content
  • Run full site locally on both Windows and OSX
  • Deployed on push
  • Be able to easily customize theme
  • RSS feed

I want control over the whole stack even though it’s harder so I get the experience running it and to not be beholden to anyone elses questionable decisions and price increases. It’s also very important that you own your own content in the sense that you’re not locked into any platform and can find better alternatives like this whole post is about.

With Ghost there were a few pain points with my ideal workflow. For normal posts I would write the post in the admin interface running locally, git commit the database file and push it up. But on my server when deploying the Ghost database file would always be modified by the deployed instance which would need to be overwritten each time. I also had to be very careful with any conflicts in the database file caused by working on the site between two different computers.

For photo posts it was a little bit more involved. I would first create an empty post tagged photos in the admin interface and copy the post ID, then upload the photos to Cloudinary, then run a script with the post id and cloudinary album name that would create a markdown file with all the images from the album. From there I would add the text and do the photo layout and run another script to render the markdown and push up the HTML into the ghost post. Cumbersome, but better than editing it in the Ghost interface which isn’t meant at all for photo gallery pages.

Hugo Time#

Now 3 years behind in updates again and not wanting to go through another painful Ghost upgrade, I figured it was a good time to simplify things so I started looking for a static site generator for blogs. I remember looking at Jekyll (slow and didn’t want to get into the Ruby ecosystem) and Gatsby (seemed too complex and heavy with dependencies and the whole GQL toolchain) but settled on Hugo for being simple and fast. Looking back at the timeline when this happened I see Astro was past it’s 1.0 release at the time but I had never heard of it then and it didn’t come up on any of my searches.

To migrate I followed a post to get me started, I think it was this one, that at least got my content exported and into the Markdown format Hugo needed. From there though things became harder as I started to port my theme over. Learning the hugo syntax took tons of googling and had many moments of “what language am I writing again?” even if for different reasons.

Since I couldn’t figure out how to customize the HTML generated from markdown for the photos section I had to spend hours figuring out how to get nested shortcodes with parameters to generate photo rows and end up with something not all that readable like

{- $innerRows := split (.Inner) "<img" -}}
<ol class="clearfix photo-row">
  {{ range $innerRows -}}
  {{- $notEntirelyWhitespace := findRE "\\S" . 1 -}}
  {{- if $notEntirelyWhitespace -}}
  <li>
  {{ printf "<img" | safeHTML }}{{- . | safeHTML -}}
  </li>
  {{- end -}}
  {{- end -}}
</ol>

In the end though I got through it of course, launched the site, and enjoyed the fast build times and minimal dependencies.

… but the itch came back to do better

Astro to the Rescue#

There’s so much more fun stuff I want to do with the site (like hiding some easter eggs) and posts with interactive components. But modifying the Hugo theme and writing raw JS for posts components was just too much of a hassle. The best way to get yourself to do something more is to remove as much friction from the process as possible. And that’s what I set out to do with this latest rebuild. I wanted to focus on developer experience and extensibility while still keeping the site statically generated for max performance.

I considered building it in SvelteKit after my experience building Roch Dog with it but currently SvelteKit is all or nothing when it comes to JavaScript page hydration. I didn’t want to go this route since I wanted to be able to control what JavaScript lands on the page and when, as well as have the site be as functional as possible without any scripts loaded.

After seeing Astro mentioned in a couple different posts I looked into it more and saw that it’d be a great fit for what I was looking for: static site generator meant for blogs with the ability to drop in interactive components into MDX posts.

With a bit of searching through Astro Blog Templates I found this one that resonated and I was off to the races

saicaca
/
fuwari
Waiting for api.github.com...
00K
0K
0K
Waiting...

Porting the content was pretty straightforward since it was in Markdown already (albeit with a slightly different frontmatter format) but it did take a little bit of time to figure out how to make a Remark/Rehype plugin for photo posts. This is an exciting and powerful feature of the new site though. It allows me to customize the rendered HTML any way I want based on simple markdown in the posts. For example, one plugin that came with the theme is for the GitHub card you see above which in the source is just:

::github{repo="saicaca/fuwari"}

For photo posts I wanted to make it as easy as possible to arrange the images similar to how I had it set up back in the Ghost days. Writing a Rehype plugin lets me do this by looking at the generated HTML and modifying it based on some rules. Here’s what the Markdown looks like for the first 4 photos in the Cambridge Carnival post:

![](https://res.cloudinary.com/dillonshook/image/upload/v1725847736/cambridge-carnival/DSC01632.jpg)
![](https://res.cloudinary.com/dillonshook/image/upload/v1725847737/cambridge-carnival/DSC01643.jpg)

1. ![](https://res.cloudinary.com/dillonshook/image/upload/v1725847738/cambridge-carnival/DSC01655.jpg)

![](https://res.cloudinary.com/dillonshook/image/upload/v1725847739/cambridge-carnival/DSC01670.jpg)

The images first get processed by a plugin I wrote for Cloudinary images that adds the dynamic sizes and srcset’s, so each image becomes something like

<img src="https://res.cloudinary.com/dillonshook/image/upload/f_auto,q_95,c_scale,w_800,dpr_auto/v1725847739/cambridge-carnival/DSC01670.jpg"
   srcset="https://res.cloudinary.com/dillonshook/image/upload/f_auto,q_95,c_scale,w_600,dpr_auto/v1725847739/cambridge-carnival/DSC01670.jpg 400w,
          https://res.cloudinary.com/dillonshook/image/upload/f_auto,q_95,c_scale,w_1000,dpr_auto/v1725847739/cambridge-carnival/DSC01670.jpg 1000w,
          https://res.cloudinary.com/dillonshook/image/upload/f_auto,q_95,c_scale,w_1800,dpr_auto/v1725847739/cambridge-carnival/DSC01670.jpg 1200w"
          sizes="(max-width: 720px) 100vw, 100vw" loading="lazy" class="post-image" alt="" title=""
>

Then a second plugin looks at how the images are arranged and adds the markup needed to lay them out. The first two are in the same paragraph so they get turned into a photo row, the third one is alone but inside a list which gets turned into a full width image, and the third is alone but not in a list so it’s a centered image. If I want to build more complex layouts in the future I can create components to use inside the MDX which is a very good escape hatch to have, but it’s very nice to have all the basics covered without needing to import components. As it is it’s very easy to re-arrange a photo page as I’m creating it with just a few keystrokes to add/remove blank lines and the 1. list indicator.

New Features#

With all that I’m really happy with how the site turned out as it stands right now as I’m about to launch it. There’s plenty more to expand upon and optimize but the foundation is there and I got a ton of great new features (in addition to all the DX ones for me) in the meantime you can check out!

  • Light and Dark mode
  • Site wide configurable hue randomized for new visitors (powered by oklch)
  • Search! That’s updated each build and doesn’t rely on a 3rd party
  • Nice archive timeline view for blog posts (scroll down)
  • Seamless page transitions progressively enhanced with swup
  • Easy to add icons via IcĂ´nes
  • GitHub cards like above and admonitions like below
NOTE

This is a special little admonition component that only takes ::: to create

In Closing: Why You Should Have Your Own Site#

If you work on the web in any capacity (yes backend engineers, I’m also talking to you) I can’t recommend strongly enough to set up your own site to play around with. It’s your personal playground to experiment with, learn new skills, and see what maintaining software over the long term is really like, even for a seemingly simple site. This is the only project I’ve worked on for over a decade that spans multiple companies and era’s of the web since I got started.

The freedom to try new libraries/stacks/languages without any approval process or judgement is great. The best way to form a real opinion is to try it out yourself and see what you love and hate. Typically companies aren’t changing their languages or whole stacks very often so if you’re only getting experience from work then your chance to try new things is basically limited to when you switch jobs. I don’t think you should go chasing every new shiny object piece of tech but it is important to experience what’s out there and how it changes your development experience.

P.S. Seems like there’s been a lot of site rewrite posts coming up lately!

Want the inside scoop?

Sign up and be the first to see new posts

No spam, just the inside scoop and $10 off any photo print!