This page looks best with JavaScript enabled

Switching to Hugo This New Year

 ·   ·  β˜• 8 min read

Finally I have decided to buy me a domain. But before that I must keep my content ready.

Earlier on, I was decided on to giving my website a go backend, but I later thought it was an overkill to use golang as a backend for a personal website and I should keep that enthusiasm for a real app. Instead of manually creating a frontend for my site, I will be using already available Hugo theme. I will be migrating my blog which I’m maintaining at codicious.wordpress.com and fxcious.wordpress.com.

Reasons

Hugo has already done a great job to steal users from WordPress. Reasons are already well known, but I’ll jot it down briefly. Here are the points:

  • It’s free, no hosting to pay for. Not only Hugo is free, it can be hosted free too. I’ll be using GitHub Pages or Netlify.
  • No server side dynamics. As there are no server side dynamics involved, there is less attack vector.

I have no real gain to migrating to the Hugo, I’m just going with the trend. Also, by using Hugo, I’ll be able to keep close to the go community which already is a language of 2020s.

Concerns

SEO Crisis - It’s not a big problem to me at the time of migration as I have not a lot of visitor. But if your site have a lot of traffic, you might wanna keep your old installation of WordPress inact to give search engines a transition to keep your SEO rank maintained.
Update: Google has nice tools and tips to avoid this SEO. To avoid it, one should register your new domain on Google Webmaster Tools.

Image Captions - Image captions would go away.
Update: Use figure shortcode. You can always create custom shortcode.

Analytics - In wordpress hosted version, we can see how many people are visiting us, and where they are from and verious democratic data.
Update: Use Google Analytics. There’s a inbuilt template which you can include in your header after writing anatytics id to config.toml. This has a drawback. When you see your website yourself (local development), you put a lot of shit out there on the Google Analytics dashboard. There are a lot of workaround though.

RSS Feed -

Migration Process

I went through a lot a blog posts to see what people have already faced during and after migration. But whatever you say man, writing blog in vim is fun. πŸ˜‰

Among others is a WordPress to jekyll blog engine converter exitwp.py (also works fine with Hugo). It is written in Python and works on exported xml file. Download the repo:

1
2
git clone https://github.com/thomasf/exitwp.git
cd exitwp

Enable a virtual environment and install the requirements:

1
2
3
pipenv --two
pipenv shell
pip install -r pip_requirements.txt

Keep the xml file in wordpress-xml directory (can put multiple files), and:

1
python ./exitwp.py

Copy the created .markdown files in _posts directory to to /contents folder.
I also renamed all .markdown extension to .md:

1
find . -type f -exec rename .markdown .md {} \;

Other method includes wordpress-to-hugo-exporter. This method assumes a install of WordPress on your own server. One thing I was not aware of before migration was, even if you don’t host your blog at your own, you can download the xml from your wordpress.com website and import it to local install and proceed as a normal. I haven’t tried this method.

If you have a nodejs background, you might like blog2md which also works on exported xml file. Modify wordpressImport according to will.

No matter what script you use for migration, it will not give you the desired result. There will be some tune up needed, which I’ve discussed in cleanup and tuning process. But just to let you know, the site just runs if you run it in deployment. So maybe one you can proceed to deployment section and publish it first then continue back from here.

Also, I found out that drafts are not converted to hugo format. And I had to manually copy paste the content.

Note on Comment Migration

Although I’ve not tried, Alessandro Bahgat in his blog post says:

Disqus comments are associated with page URLs, so you will want to make sure your pages are served at the same URLs as before the migration. Alternatively, you can edit the URLs in the export file before importing it following the instructions above.

Note on Image Migration

  • Downscale images.
1
2
find . -name "*.jpg" -exec jpegoptim -m80 -o -p --strip-all {} \;
find . -name "*.png" -exec optipng -o7 {} \;
  • Git LFS should be enabled to keep git repo efficient as git is not meant to track binary files.
1
2
git lfs install
git lfs track "*.jpg"
  • For me, images came up like this after conversion:
1
2
3
[caption id="attachment_229" align="aligncenter" width="234"]
[![Pivot Point Menu](https://fxcious.files.wordpress.com/2018/06/pivot-point-menu.jpg)](https://fxcious.files.wordpress.com/2018/06/pivot-point-menu.jpg) Pivot Point Menu
[/caption]

It needs to be converted like this:

(Are you wondering why only the above piece of code is hosted on gist and rest natively? Seems like hermit does not support shortcode inside code blocks; or maybe it is by desigh, who knows).

  • If you have featured image per post, it goes like this in front matter:
1
2
images:
  - https://picsum.photos/1024/768/?random

These images is also used in Twitter Cards and the Open Graph metadata.

  • Use relative url from static folder.

Cleanup Process

No matter what method you used above for data extraction, your isn’t going to be perfect. You might semi-automate some of these with tools like or text editor.

Front Matter Cleanup

This is how a typical blog post looked like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
author: sntshkmr60
comments: true
date: 2019-09-01 15:28:00+00:00
excerpt: <stripped>
layout: post
link: https://codicious.wordpress.com/2019/09/01/run-unit-test-before-every-commit-with-pre-commit-hook/
slug: run-unit-test-before-every-commit-with-pre-commit-hook
title: Run Unit Test Before Every Commit with pre-commit Hook
wordpress_id: 306
categories:
- software-development
tags:
- deployment
- git
- webdev

Obviously I don’t need all of them. Following are the things I’m planning to throw away:

  • author - As I’m the only author of this blog, it’s redundant.
  • wordpress_id, excerpt, link, layout - I don’t find any use of them.
  • categories - categories are by default disabled, tags are enough though.
  • slug - title is enough to generate URLs
1
sed -i '/^slug:/d' content/posts/*

Janik Vonrotz has covered a lot of these automation if not all in his blog post: https://janikvonrotz.ch/2018/07/08/another-wordpress-to-hugo-migration/

Content Cleanup

  • I checked if code blocks are converted properly and placed correctly (they were not).
  • Convert youtube, twitter, image shortcodes to Hugo equivalent. See https://gohugo.io/content-management/shortcodes/
  • Check internal links, make them relative. For images, I’ve replaced https://codicious.files.wordpress.com/ and https://fxcious.files.wordpress.com/ with /uploads/ wherever image is being referenced. And downloaded and kept them inside /static/uploads folder. Inside uploads directory I’ve maintained YYYY and MM subdirectories so that one single directory is not cluttered up.

We missed

I didn’t think of these parameters but these also matters. I have not implemented them yet. I’m planning to work on it when the site has started rolling.

Search (pending)

Many Hugo users employ Google Custom Search. But it contains ads (in the free edition). That’s why I’m using a simple HTML form and redirect to Google Search but restrict the search to my site.

1
2
3
4
5
<form role="search" method="get" action="https://www.google.com/search">
  <input type="search" placeholder="Search..." value="" name="q" title="Search for:">
  <input type="hidden" name="sitesearch" value="santoshk.dev">
  <button type="submit" value="Search"/>
</form>

Update: Since version 0.27, Hugo provides a powerful solution for related content out of the box. One needs to add a related content section to all single posts page, like below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="related-posts">
  <p class="h3-like">Related Posts</p>
  {{ $related := .Site.RegularPages.Related . | first 4 }}
  {{ with $related }}
  <ul>
    {{ range . }}
    <li>
      <a href="{{ .Permalink }}">
        <img src="{{ .Params.featuredImage }}" alt="{{ .Title }}"/>
        <p>{{ .Title }}</p>
      </a>
    </li>
    {{ end }}
  </ul>
  {{ end }}
</div>

Deployment Process

Initially I was going to host my website at Github Pages, but while this entire process I came over Netlify. It gives more control over the deployment process. It also works directly with my local Hugo setup and I don’t need another repo with generated html content.

  1. I added these lines in a file called netlify.toml in the root of the repo:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    [build]
    publish = "public"
    command = "hugo --gc --minify"
    
    [context.production.environment]
    HUGO_VERSION = "0.59.1"
    HUGO_ENV = "production"
    HUGO_ENABLEGITINFO = "true"
        
  2. I signed up to Netlify with my git hosting provider by which I was hosting your Hugo repo.

  3. Setting up a new Hugo site was pretty easy and straight forward. At last I selected my brach to which netlify will be looking for new updates.

More information about Netlify deployment can be found at https://gohugo.io/hosting-and-deployment/hosting-on-netlify/.

This was a journey from WordPress to Hugo, and was exaustive actually. Anyways, migrations are always painful than starting from scratch. And Hugo isn’t only used for blogging. I found out a theme which makes it easier to create dotumentation/book/tutorial.

Share on

Santosh Kumar
WRITTEN BY
Santosh Kumar
Fullstack Developer at Method Studios