Shane A. Stillwell
Migrate from Hugo to Astro

Migrate from Hugo to Astro

Yeah, like Marc, I’ve wasted lots of time moving from one blogging platform to the next. I’ve been blogging for going close to twenty years. You see the first post of this blog. I’m not sure if that’s the official first. But it’s the oldest I have on record.

I’m pretty sure the blog has gone through this iteration.

  1. Wikipedia ~2005
  2. Drupal ~2006
  3. WordPress ~2010
  4. Hexo ~2013
  5. Hugo ~2017
  6. Astro ~Nov 11, 2023

I’ve notice that my blogging slows down a little more every year. Life and work get in the way. Sitting down to write a blog takes time and I have less of that as each day passes.

Today, you are as old as you’ve ever been, and as young as you ever will be.

At first, the blog was just my way of keeping track of problems I’ve encountered along the way and how to solve them. I very clearly remember solving a problem and recalling that I had the exact same problem a year earlier, but forgot the solution. Hence, the birth of this journal. I’m mostly writing for me. You can see along the way the advice has slowly changed, and the posts a little more thoughtful.

Steps I took to migrate from Hugo to Astro.

  1. This is embarrassing, but I’ve tried to migrate this blog from Hugo several times. I most got hung up on the fact that Hugo is content based and up until recently, Astro was only page route based. What does that even mean? Well, before Astro developed their Content Collections feature, all your content needed to be under the /pages folder and had to be organized how you wanted to route them.
  2. Finally, with a little more knowledge than when I started, I was able to solve the problem. One was moving my Hugo blog to just straight URLs rather than the WordPress default /year/month/day/slug I was using with Hugo.
  3. Pick a theme that uses the Content Collections. I liked the EV0 Astro Theme. It was simple and had some nice featured baked in. From here you can just clone that repo with git clone [email protected]:gndx/ev0-astro-theme.git, then cd ev0-astro-theme, npm install, npm run dev and start working.
  4. Pull out your content from hugo, which for me was in the content/post folder. I moved the entire post folder to its own location so I could go through it and fix things.

NOTE about my Hugo content

I was using the directory per post approach of Hugo, so each post looked like this structure

🔻 📂 a-better-password-reset-token
  🔻 📂 images
      📄 old-thumbnail.jpg
      📄 thumbnail.jpg
    📄 index.md

Writing scripts to get my Markdown as close as possible.

This is the command I ran a few times while I was tweaking the ETL1

rm ./content && # remove the existing folder
mkdir content && # recreate the folder
cp -a ./hugo/content/post/* ./content && # copy the content from Hugo to this upper folder
mkdir content/NEWFILES/ && # This is the folder that holds the converted Markdown files
touch content/NEWFILES/index.md && # This was needed so the script didn't blow up
python3 convert-subs.py && # Convert subdirectory 
rm content/NEWFILES/newfiles.md && # file created by one of the scripts, just remove it, it causes issues with Astro
python3 convert-front-matter.py && # Convert some frontmatter dates and descriptions
cp -r ./content/NEWFILES/* ./ev0-astro-theme/src/content/blog/ # copy the converted files into Astro

Here are some of the python scripts used above, YMMV

# convert-subs.py
import os

def convert_subdirectories_to_files(directory_path):
    for root, dirs, files in os.walk(directory_path, topdown=True):
        for dir_name in dirs:
            print(dir_name)
            # Form the old directory path and the new file name
            old_dir_path = os.path.join(root, dir_name)
            new_file_name = dir_name.lower() + ".md"
            # Form the new file path by joining the root directory with the new file name
            new_file_path = os.path.join(root, 'NEWFILES', new_file_name)
            # Rename the index.md file to the new file path
            os.rename(os.path.join(old_dir_path, "index.md"), new_file_path)
            # Remove the now empty old directory
            # os.rmdir(old_dir_path)
        break

# Usage:
convert_subdirectories_to_files('./content')

# convert-front-matter.py
# Need to run `pip3 install python-frontmatter`
import frontmatter
from datetime import datetime
import os

def process_markdown_file(file_path):
    # with open(file_path, 'r') as file:
    #     content = file.read()

    post = frontmatter.load(file_path)
    # Access and modify the desired frontmatter properties as needed
    if post['date'] and type(post['date']) == str:
        post['pubDate'] = datetime.fromisoformat(post['date']).isoformat()
    else:
        post['pubDate'] = post['date'].isoformat()

    post['description'] = post['title']
    # if hasattr(post, 'tags'):
    #     post['tags'] = post['tags']
    # else:
    #     post['tags'] = []

    new_content = frontmatter.dumps(post)
    # print(new_content)

    with open(file_path, 'w') as file:
        file.write(new_content)

def convert_properties_in_markdown_files(directory_path):
    for root, dirs, files in os.walk(directory_path):
        for file in files:
            if file.endswith('.md'):
                file_path = os.path.join(root, file)
                process_markdown_file(file_path)

# Usage:
convert_properties_in_markdown_files('./content/NEWFILES')

Fixing the images

Sorry, I don’t have any good advice here. I just brute force added the images for the post covers. I still have a whole bunch of broken images I need to fix, but that will come in time.

After the files were converted and in Astro, then came the hard work of making it my own and fixing some of the issues.

YouTube (short codes)

I had a short code for embedding YouTube videos, but with just sticking with Markdown, I found this handy little suggestion for linking to YouTube videos. It will grab the thumbnail for the video and link to the video. Nice.

[![The Last Lecture](https://img.youtube.com/vi/j7zzQpvoYcQ/0.jpg)](https://www.youtube.com/watch?v=j7zzQpvoYcQ)

Final Product

If I haven’t migrated away to a different publishing platform, you can see the code for this blog on GitLab. Now I’m using GitLab pages to publish the post (cached by CloudFlare). You could use CloudFlare to host or even Netlify. All good options in their own right.

Lessons learned.

  1. Eschew custom items for a publishing platform (e.g. Hugo’s directory per post approach) or Astro’s MDX. It just causes migration headaches.
  2. Counter to that point, don’t migrate… just publish. Honestly, if I wasn’t curious about learning new site generators like Astro, I wouldn’t even bother. I would just use Ghost or Write.as.
  3. Counter to that last counter, own your own content.
  4. Is Markdown driven content better? I’m not sold, but the alternative is a database and that comes with its own cost, both literally and cognitively. It’s much easier to press a button for a new Blog post, but I really like that I can build my blog into a static site and serve that for free from any of the hosting options.

1 Extract. Transform. Load.