I’ve been out of a job for a few days now and between scanning for offerings and submitting my resume I also got enough time to finally migrate my rusty WordPress blog to Hugo. It’s been on my mind for a while now but I could never find the time or the will to do it.
Until now.
Why move from WordPress?
The reasons why people move away from WordPress to Hugo (or other static site generator) are well know by this point so I won’t go over it.
Each scenario is different but I can summarize my own reasons to move:
- Tired of maintaining and securing a WordPress instance
- Got locked out of my own account one too many times (it was secure alright)
- Cost savings. By moving to static files I can save on storage and CPU
- I host my website on NearlyFreeSpeech and although it’s already dirt cheap I can make it cheaper
- I get to replace a ‘web based’ workflow with a Git one. Much more familiar since that’s what I do day in day out
- I posted less and less and one of the explanations was too much friction caused by WordPress
- Vim all the things!
How?
Moving from Wordpress to Hugo is also very well documented and it basically goes like this:
- Export WordPress posts to Markdown
- Setup Hugo, pick a theme and massage the
.md
files until satisfied - Publish
- Done
In my particular case I took the following path:
- Install Jekyll Exporter and use it to extract the blog’s content
- Run
hugo import jekyll
to slurp the files I exported and convert them to the target format - Pick and configure a theme. I chose typo by Francesco Tomaselli because it looks really good and sports a decent amount of features.
- Once happy with the first version create a new repo on GitLab and push the changes
- Backup
blog/
just in case - Setup a CI/CD pipeline to build and rsync the files to the web host (not forgetting the
--delete
flag to get rid of the old version) - I can now die happy
The plan was in motion!
Until I hit a snag on item #4.
Oh sh*t!
I had already created a ‘blog’ repository. It’s even got CI/CD pipelines set up! What in the world…
It took a while for that newly acquired information to sink in.
My brain hurriedly started to pick tattered memories from the floor and shoving them into my lap - “You went through all of this already! Look, you even picked a theme, set up CI/CD! It’s even live on tiagoafpereira.gitlab.io/blog. Don’t you remember?”
I didn’t in fact remember.
I quickly cloned the repo and went through the commit history. It was all there.
Update sandbox.md and theme css Tiago Pereira 3 years, 1 month ago
Update sandbox.md Tiago Pereira 3 years, 1 month ago
Add sandbox.md test post Tiago Pereira 3 years, 1 month ago
Add RSS link to header and fix GoatCounter tag Tiago Pereira 3 years, 1 month ago
Add 2021-07-30-hugo-tooling.md draft Tiago Pereira 3 years, 1 month ago
Update 2020-03-30-migrating-from-wordpress.md Tiago Pereira 3 years, 1 month ago
Update 2020-03-30-migrating-from-wordpress.md Tiago Pereira 3 years, 1 month ago
Update About (syntax error) Tiago Pereira 3 years, 1 month ago
Update About Tiago Pereira 3 years, 1 month ago
Publish about pages Tiago Pereira 3 years, 1 month ago
Reverted /blog/ url element removal Tiago Pereira 3 years, 1 month ago
Revert "Fix redirects for WP ported posts" Tiago Pereira 3 years, 1 month ago
Fix redirects for WP ported posts Tiago Pereira 3 years, 1 month ago
Added About fixed page Tiago Pereira 3 years, 1 month ago
Add redirects to old URLs in migrated posts Tiago Pereira 3 years, 1 month ago
Add URL redirect to 2019-09-29-festival-iminente-2019.md Tiago Pereira 3 years, 1 month ago
Update 2020-03-30-migrating-from-wordpress.md Tiago Pereira 3 years, 1 month ago
Fix extra /blog/ segment in migrated WP posts frontmatter Tiago Pereira 3 years, 1 month ago
Update 2020-03-30-migrating-from-wordpress.md Tiago Pereira 3 years, 1 month ago
Add GoatCounter script Tiago Pereira 3 years, 1 month ago
Multiple changes: Tiago Pereira 3 years, 1 month ago
Update 2020-03-30-migrating-from-wordpress Tiago Pereira 3 years, 1 month ago
Multiple changes Tiago Pereira 3 years, 1 month ago
Update 2020-03-30-migrating-from-wordpress.markdown Tiago Pereira 4 years, 5 months ago
Set rssLimit to 10 Tiago Pereira 4 years, 5 months ago
Update configuration and relative links Tiago Pereira 4 years, 5 months ago
Change baseURL to https://tiagoafpereira.gitlab.io/blog/ Tiago Pereira 4 years, 5 months ago
Change baseURL to 'blog' Tiago Pereira 4 years, 5 months ago
Updated baseURL to match gitlab.io host Tiago Pereira 4 years, 5 months ago
Initial commit Tiago Pereira 4 years, 5 months ago
How to format your git log like this
This produces the exact same output
git log --pretty --format="%s %aN %ar %d"
An improved version including commit hash and pretty colors:
git log --pretty --format="%C(bold red)%h%Creset %s %C(bold blue)%aN%Creset %C(green)%ar%Creset %C(cyan)%d"
I had already setup the whole thing: stats, fixed redirects, theming. Even wrote a “Migrating from WordPress” post.
The other thing I noticed was that wasn’t using Conventional Commits. Three years ago that wasn’t ingrained in my muscle memory yet. Silly monkey.
What immediately pissed me off the most was that I had just wasted a considerable amount of hour on rework.
It’s even got it’s own category of dumb according to experts in the field and I wholeheartedly agree!
“Software development projects manifest nine types of waste: building the wrong feature or product, mismanaging the backlog, rework, unnecessarily complex solutions, extraneous cognitive load, psychological distress, waiting/multitasking, knowledge loss, and ineffective communication”
In the end I accepted this stupid fate of mine and just soldiered on. I took the best parts of the forgotten version and merged them into the new one.
What on π just happened?
The explanation is fairly simple: I procrastinate. A lot.
I’m also a busy person (who isn’t?) with very little time for side projects. When I do get some spare time I work on them.
Looking at the commit dates helps to reconstruct a real-life timeline that better puts things into perspective.
These were the periods of active work on the blog migration:
- 8-9 April 2020: First parental leave
- 22-31 July 2021: Took some days off in between switching jobs
- 22-now August 2024: Out of a job and looking for a new one
A bit of history
I started this blog in 2007. I wanted to show off my hard-won second place in an academic poster design contest.
I picked Textpattern to create and manage the blog until 2010 when it suddenly stopped working and I couldn’t fix it. At the time I simply gave up and replaced it with WordPress.
I don’t remember exactly why I decided on Textpattern but I believe it was because it had (and still has) a much simpler and uncluttered UI compared to WordPress.
You could also write your posts using textile and I must have found this much nicer than whatever Automattic offered at the time.
Lightweight markup languages were still in their infancy. Textile was released in 2002 and Markdown in 2004. In fact, Textile was one of the markup languages that influenced the creation of Markdown.
First migration attempt
Fast forward 10 years to 2020.
InΓͺs was one month old, I was enjoying my parental leave and thus had a reasonable amount of time in my hands.
I had this item on my TODO list: migrate the blog to something other than WordPress. The reason for this was that I wasn’t posting as often as I wanted because the whole process was really clunky.
I had streamline the posting procedure to something like this:
- Cull, edit and upload the photos to Flickr
- Run the individual photo (or album) URL trough a script to get WordPress markup containing a list of photos
- Create a new blog post, paste the generated markup and add a description
- Publish
By this time I had gotten used to life in the terminal and the thought of having to login to an (extremely cluttered) admin page, use a rich text editor to simply paste script generated content just wasn’t worth the effort so over the years I posted less and less.
This wasn’t the only reason but it was probably the strongest one. I’m sure you get my point.
Python script that builds this chart
Note: requires Plotly
import plotly.graph_objects as go
import os
# To fetch a count of posts per year:
# cd content/posts/
# grep -irl "draft:.*false" *.md | xargs grep -ir "date:.*" --no-filename | cut -c 7-10 | sort | uniq -c | awk -v OFS=":" '{print $2,$1","}'
post_count_per_year = {
2007: 14,
2008: 26,
2009: 19,
2010: 71,
2011: 42,
2012: 36,
2013: 21,
2014: 6,
2015: 2,
2016: 5,
2017: 2,
2018: 6,
2019: 2,
2024: 1, # this entry was manually set since it was still in draft
}
# get a continuous list of years from first to last post date
years = list(post_count_per_year.keys())
year_ticks = list(range(years[0], years[-1] + 1))
x = [d for d in post_count_per_year.keys()]
y = list(post_count_per_year.values())
fig = go.Figure()
fig.add_trace(
go.Bar(x=x, y=y),
)
fig.update_layout(
yaxis_title="Blog posts",
xaxis=dict(tickmode="array", tickvals=year_ticks),
margin=dict(l=5, r=5, t=5, b=5),
)
fig.add_annotation(x=2020, y=1, text="First kid", showarrow=True, arrowhead=1)
fig.add_annotation(x=2022, y=1, text="Second kid", showarrow=True, arrowhead=1)
fig.add_annotation(x=2024, y=1, text="This post!", showarrow=True, arrowhead=1)
if not os.path.exists("images"):
os.mkdir("images")
fig.write_image("images/post-frequency-chart.svg")
I just wanted something faster than this. I finally had some free time on my hands (and a new born, let’s not forget that) a running website and a computer. Me being lazy and impatient didn’t help much either.
The obvious choice was to statically generate the blog contents and replace the existing WordPress files/database.
Back in 2007 setting up a blog using a static site generator was something uncommon. Jekyll hadn’t even been created yet. I’m not entirely sure I would have used it if it had been released earlier though. At that time I was still very much a Windows user with only passing knowledge of anything terminal related.
And so I got started on the migration!
…and shortly after having extracted the contents from the old blog, picking a theme, setting up analytics and deploying it to GitLab Pages (as a staging environment) life kicked back into full gear and I promptly forgot about the whole thing.
Second migration attempt
3 years have passed and I suddenly find myself with a lot of free time on my hands. Again. Not because of another parental leave (that did happen though) but because I became unemployed.
And what happens when I suddenly get some free time? I (re)start to migrate my blog.
Some of the grunt work I was about to redo had already been done:
- Setting up redirects to ensure the old WordPress URLs are still valid and pointing to the new version of the blog
- Replace all original Flickr size image embeds with medium versions
- Tweak a new theme, again. I ended up sticking with the latest choice. It didn’t even exist 3 years ago so I guess the wait was worthwhile!
This is good news, I didn’t trash the whole thing!
There were a few things still missing though, let’s take a quick look at what I ended up doing.
Fixing legacy textile syntax
I still had 30 posts with legacy Textile syntax that I hadn’t fixed back in 2010 (or didn’t even realize the problem was there).
Stuff like links that uses a different syntax from Markdown. Luckily it’s a trivial fix. E.g.:
-Full set on flickr right "here":http://www.flickr.com/photos/tiagoafpereira/sets/72157600340379318/
+Full set on flickr right [here](http://www.flickr.com/photos/tiagoafpereira/sets/72157600340379318/)
Fixing some broken links
A blog spanning 17 years is bound to have some broken links and indeed this was the case.
I could fix some of this rot but for the most part those links are either gone for good or there’s no way for me to find their new location.
To run a quick check on all links I used a handy little tool called lychee.
lychee content -f detailed
2006/2006 ββββββββββββββββββββ Finished extracting links π Summary
---------------------
π Total.........2006
β
Successful....1960
β³ Timeouts.........3
π Redirected.......0
π» Excluded.........0
β Unknown..........0
π« Errors..........37
I fixed what little I could and moved on.
Set up GitLab action to upload artifacts to the webhost
I could host the blog on GitLab Pages but I prefer to have full control over
this part. All I need to do is replace everything under /blog
with the static
files generated by Hugo.
To set this up I created a .gitlab-ci.yml
in the project’s root folder.
This is the gist of it:
image: registry.gitlab.com/pages/hugo/hugo_extended:latest
variables:
HUGO_ENV: production
default:
before_script:
- apk add --no-cache go curl bash openssh-client rsync
- eval $(ssh-agent -s)
- chmod 400 "$NFS"
- ssh-add "$NFS"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
test:
script:
- hugo --minify
rules:
- if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH
upload_to_NFS:
script:
- hugo --minify
- rsync -avzr --delete --no-perms --no-owner --no-group --omit-dir-times -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" public/ [OMMITED]@[OMMITED].nearlyfreespeech.net:/home/public/blog/
rules:
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
Before getting everything up and running I still had to:
- Backup the old blog files (I’ve kept the DB, will eventually delete it)
- Create a SSH key (and store it as a GitLab CI/CD secret) in order to be able to rsync the files over to the host in a non interactive way
Now every time I push to master
this pipeline is triggered and the blog is
updated.
Closing thoughts
Don’t know what I enjoyed the most, migrating the blog or writing about the experience of migrating the blog.
Took a deep dive into my own memories, confirmed that I am but a mere mortal with finite time in this Earth and brushed up on my terminal and Python skills.
Also ended a 5 year rut without any new content on this site.
Next on my list:
- Figure out my strategy for photo hosting. I used to have a Flick Pro account but cannot longer justify it so I’ve switched back to Free. This means I now have a 1000 photos limit but with 16x that much in actual use. All photos in this blog are stored on Flickr’s servers and each one has got it’s death sentence set.
- Come up with a streamlined method of writing new blog posts.
- Keep posting!
- Get a job! (almost forgot this one)
We are stuck with technology when what we really want is just stuff that works.