In this post, I will talk about how I migrated away from hosting my blog on Medium to having my own static pages written in Markdown. I used Claude Code to help design my new setup and plan the migration. I will walk you through my process so it can be useful for humans and bots alike ;). While this may sound straightforward, you still need a bit of comfort using your terminal, git-based workflows, and Markdown to consider this option.

Why migrate away from Medium#

Medium was a great starting point for writing, but over time a few things started to bother me. Here are the main reasons I decided to move on.

  • Ads — I started noticing Medium pushing more ads into the reading experience. I just wanted my content out there without ads interfering with the interface.
  • Noise over signal — I did not find much value in the comments and reactions sections. I wanted a space focused purely on content.
  • ADHD-friendly writing — I have ADHD, and I have slowly moved my workflows to be Markdown-friendly. It keeps me from getting distracted by ornamental formatting decisions. As a programmer, Markdown is also a natural fit.
  • Personal brand — A good friend of mine talks a lot about building your own personal brand. Having a lightweight static site of my own felt like a meaningful step in that direction.
  • Code highlighting — Code highlighting on Medium is rather bland, with no syntax colors to speak of. My workaround was to paste code into a GitHub Gist and embed it from there just to get decent highlighting. Markdown gives me proper fenced code blocks with syntax highlighting out of the box, which is a big deal for a technical blog.
  • LLM-powered writing tools — I want to use LLM-based productivity tools to help with writing and evaluating content. Staying in a Markdown and terminal-based workflow makes it easy to keep using Claude Code, which has been my primary coding assistant for the past 6–7 months.

The entire migration — everything I will cover in the rest of this post — took me roughly 60 to 90 minutes end to end. It also helped that I only had 7 posts to migrate. Yesterday’s laziness is today’s fast migrationness.

What is my tech stack#

Hugo powering Markdown#

For the static site generator, I went with Hugo. Hugo is one of the fastest static site generators out there — it takes your Markdown files and spits out a fully built HTML site in milliseconds. There is no database, no server-side runtime, just files. This makes it incredibly lightweight to host and easy to version control.

The project structure is straightforward:

my-blog/
├── content/
│   └── posts/        # Your blog posts as .md files
├── themes/           # Your chosen Hugo theme
├── static/           # Static assets (images, fonts, etc.)
└── hugo.toml         # Site configuration

Each post is just a Markdown file with a small block of metadata at the top called front matter. Hugo handles the rest — generating pages, navigation, and applying the theme. There are hundreds of community themes to choose from, so you can get something that looks good without writing any CSS. For this blog I am using the Terminal theme by panr — credit where it’s due.

Cloudflare for hosting#

For hosting, I evaluated GitHub Pages but ultimately went with Cloudflare Pages. GitHub Pages is a perfectly solid option, but I already had another application hosted on Cloudflare and was familiar with the dashboard. That familiarity alone made it the easier choice.

Cloudflare Pages also made custom domain setup painless, which was important to me — I wanted the blog to live at my own domain rather than a subdomain of someone else’s platform. The integration with GitHub means deployments are fully automated. I will cover the exact deployment workflow in the last section.

Both Cloudflare Pages and GitHub Pages are free, and the free tiers are generous enough that a personal blog will never come close to hitting the limits (though I sincerely hope I reach the limits without bot and agentic traffic :D ). For reference:

  • Cloudflare Pages free tier — unlimited bandwidth, unlimited requests, up to 500 builds per month, and up to 20,000 files per site.
  • GitHub Pages free tier — 1 GB site size, 100 GB bandwidth per month, and 10 builds per hour (though this limit does not apply when using a custom GitHub Actions workflow, which is what we will set up).

The only real cost in this entire setup is the custom domain. Cloudflare sells domains at cost with no markup, so it is about as cheap as domain registration gets. Everything else is free.

Steps to migrate blogs#

Step 1: Create a repository and scaffold with Hugo#

The first step was getting a Hugo project up and running. This was pretty straightforward — I asked GitHub Copilot to generate the initial skeleton for me, including the basic folder structure and config file. I also shared links to my existing Medium posts so Copilot could generate skeleton Markdown files for each one, which gave me a starting point to work from rather than pasting everything from scratch.

Step 2: Select a theme#

Hugo has a large library of community-built themes available at themes.gohugo.io. You can browse by style — minimal, magazine, documentation, terminal-style, and more. Themes are added as git submodules, so swapping one out later is easy enough. Pick one that matches how you want your content to feel, drop it into the themes/ directory, and reference it in your hugo.toml. That’s it.

Step 3: Setting up Cloudflare Pages#

Setting up the Cloudflare Pages project is mostly straightforward, with one slightly awkward step. When you create a new Pages project, Cloudflare’s UI expects an initial file upload to get past the setup screen. Since we are deploying via GitHub Actions rather than a direct upload, the workaround is to do a one-time manual upload of a placeholder file just to get through that screen. After that, automated deployments take over and you never touch the UI again for deploys.

Once the project is created, linking a custom domain is a matter of adding it in the Pages project settings and pointing your DNS records at Cloudflare. If your domain is already on Cloudflare, this takes about two minutes.

Step 4: Setting up deployments#

As a programmer, sloppy deployments bother me. The goal was a clean, fully automated pipeline — push to main, site updates. No manual steps.

One-time setup#

Before the workflow can run, you need to configure a few secrets and variables in your GitHub repository settings:

  • CLOUDFLARE_API_TOKEN — an API token from your Cloudflare dashboard. This one tripped me up a bit. When creating the token, you need to create a custom token (not use one of the presets) and explicitly grant it Cloudflare Pages — Edit access. A token that is too broad or too narrow will cause the deployment action to fail with a permissions error. Once you scope it correctly to just Pages edit access, it works fine.
  • CLOUDFLARE_ACCOUNT_ID — your Cloudflare account ID, found in the dashboard sidebar
  • SITE_URL — a repository variable (not a secret) set to your custom domain, e.g. https://blog.gauravsingharoy.net/

The deployment workflow#

Here is how the pipeline works end to end:

flowchart TD
    A[Push to main] --> B[GitHub Actions triggered]
    B --> C[Checkout repo
with submodules] C --> D[Setup Hugo] D --> E[Build site
hugo --minify] E --> E_note[/outputs static site to public\//] E_note --> F[Deploy public/ to
Cloudflare Pages]

The GitHub Actions workflow that drives this is straightforward. The key steps are:

- name: Setup Hugo
  uses: peaceiris/actions-hugo@v3
  with:
    hugo-version: 'latest'
    extended: true

- name: Build site
  run: hugo --minify --baseURL "${{ vars.SITE_URL }}"

- name: Deploy to Cloudflare Pages
  uses: cloudflare/pages-action@v1
  with:
    apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    projectName: tech-blog
    directory: public

Hugo builds the site into the public/ directory, and the Cloudflare Pages action picks that up and deploys it. That’s the entire pipeline. Every push to main goes live automatically.

Step 6: Migrating the old posts#

With the pipeline in place, it was time to actually move the content over. I had 7 posts on Medium that needed to end up as Markdown files. Simple enough in theory.

My first instinct was to be efficient about it. I created a separate GitHub issue for each post and assigned them all to GitHub Copilot. What could possibly go wrong with such a manual task?

As it turns out — quite a bit. Copilot ran into access issues trying to crawl Medium pages, which is fair enough. What was less fair was what happened next: rather than stopping and flagging the problem, it hallucinated and started creating new blog post entries based on some of my GitHub repositories. I am genuinely unsure whether to be scared by that or impressed by the initiative.

So I moved to Claude. Claude also struggled to crawl Medium directly — same access issues (duh!). I had to step in. I downloaded each of my 7 posts as single-page HTML files (and I was quietly thankful I had just 7 posts to deal with), then handed them to Claude Code and asked it to generate Markdown files for each one. That worked cleanly. The conversion came out well-structured and required only minor cleanup to get everything into shape.

Conclusion#

Migrating away from Medium turned out to be one of those tasks I had been putting off for longer than it deserved. The actual work — setting up Hugo, picking a theme, configuring Cloudflare Pages, and wiring up automated deployments — took me less than 90 minutes end to end.

To recap: I scaffolded the project with Hugo and used GitHub Copilot to speed up the initial skeleton, picked a theme from Hugo’s library, set up a Cloudflare Pages project with a custom domain, configured a GitHub Actions workflow to handle deployments automatically on every push to main, and finally migrated the existing posts by downloading them as HTML and letting Claude Code convert them to Markdown.

If you are a developer who has been sitting on the fence about moving off Medium, my honest take is: just do it. You own the content, you own the stack, and the setup is far simpler than it looks. The only thing I would do differently is migrate sooner.