Thumbnail image

Medium to GitHub using Hugo - Why and How I Migrated my Blog

Why not Medium

My main reason for migrating off Medium was the paywall Medium introduced while back. I actually understand why they did it. The unlimited access price: $5/month ($50/year) is too high, but still, I get it.

For me though, the objective was to allow readers to easily discover and read my posts. I don’t want my readers to experience any friction. Forcing the reader to deal with the frustrating Medium up-sell pop-ups just to read my post was just unnecessary.

Why Hugo on GitHub

I chose Hugo because it was the simplest, most flexible, and fastest static site generator I could find. It allowed me to use the technologies I already was familiar with (e.g. Markdown). And, it can be easily deployed to any number of hosting targets (e.g. including GitHub Pages for free!).

The other properties of Hugo that contributed to my decision were:

How I Migrated content

The process of migrating form Medium to Hugo was a lot more complex than I expected. It was mainly related to some idiosyncrasies of how Medium exports images and formats code samples in its HTML files. Hope my learnings captured in this post are helpful.

Medium provides a quick way to export all of your personal content as HTML files in a single .zip file. The entire process for either desktop of mobile is documented here.

Once I had my content downloaded locally, I had to convert the posts into Markdown (it’s how posts are defined in Hugo). I’ve tried a few tools and the medium2md CLI did the best job. After the conversion, I had to manually modify the Hugo metadata generated by my HTML to Markdown conversion tool. Here is an example of the metadata for this post:

title: Medium to GitHub using Hugo - Why and How I Migrated my Blog
date: '2022-01-03T13:51:00.022Z'
categories: [dev]
tags: [hugo, github, blog]
thumbnail: "/images/hugo.png"
draft: true

Note: medium2md requires Node. The only other migration tool I found that wasn’t Node based was medium-to-hugo but it hasn’t been updated for over 3 years.

Once I had my posts in Markdown format and all the images exported, I’ve used the Hugo Quick Start docs to create my site. Most of the steps were easy to follow. I’ve spent over two hours selecting the theme for my blog, but that has to do more with the amount of free themes that are available and my inability to make a decision.

The one thing that made the whole customization a lot easier was the live reload feature in Hugo. When started as a server in the developer mode (hugo server -D), Hugo allowed me to edit the different configuration options as well as post content and instantaneously view them in the browser without reload or refreshing of the pages. This made the whole experience a lot easier and faster.

Once I was satisfied with how my blog looked, I moved on to deploying the blog to GitHub. Hugo has a doc on how to publish your blog to GitHib pages which keeps everything in a single, public, repo. I’ve opted to keep my Hugo project in a private repo called blog, and use GitHub Action to publish only the generated content to another repo (

To do this I had to edit the Hugo workflow to provide parameters for the external_repository and configuring a deploy key on the repo. The standard GITHUB_TOKEN has no permission to access to external repositories.

      - name: Deploy Blog
        uses: peaceiris/actions-gh-pages@v3
        if: github.ref == 'refs/heads/main'
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          external_repository: mchmarny/
          publish_branch: main
          publish_dir: ./public
          commit_message: ${{ github.event.head_commit.message }}
          allow_empty_commit: true

Notice the cname setting in the above YAML. It’s required to setup the SSL cert for the custom domain in my blog. More on setting up custom domain in GitHub pages here.

As a result of this configuration:

  • Site generation workflow is triggered with each commit to the blog repo
  • I can write and edit posts on my iPad just by creating a new Markdown file (*.md)
  • New posts in draft mode are not being discoverable in public site (draft: true in hugo prevents the public content from being generated)
  • Static content (no DB) means more secure site (i.e. how often do you hear about Wordpress vulnerabilities)

I hope you found my experience of migrating from Medium to Hugo helpful.