Migrating from Hugo to org-mode

Table of Contents

I've been wanting to migrate the blog from Hugo to org-mode for quite a long time now. This weekend I decided it was time to finally tackle it, here is how I did it.


First of all, all it's needed for publishing a blog on org-mode is emacs, nothing else. I also wanted to be able to automatically deploy new posts whenever new content is pushed to the repository.

To be able to achieve all this, I needed to set up an org-mode project and create a Github action, the next sections explains how to do it.

Set up and publish an org-mode project

In order to set up the project, I followed two good resources, the own org-mode documentation on Publishing and an introduction to Publishing Org-mode files to HTML. At the end I ended up with this piece of code:

(require 'ox-publish)
(setq org-html-head-include-default-style nil)
(setq org-html-htmlize-output-type 'css)

(setq org-publish-project-alist
         :base-directory "."
         :base-extension "org"
         :exclude "static/.*"
         :publishing-directory "public/"
         :recursive t
         :publishing-function org-html-publish-to-html
         :headline-levels 6
         :section-numbers nil
         :auto-preamble t
         :auto-sitemap t
         :with-toc t
         :sitemap-filename "sitemap.org"
         :sitemap-title ""
         :sitemap-sort-files anti-chronologically
         :makeindex t
         ;; :html-use-infojs t
         :html-preamble t
         :html-postamble t

         :base-directory "."
         :exclude "static/.*"
         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
         :publishing-directory "public/"
         :recursive t
         :publishing-function org-publish-attachment
        ("org" :components ("org-notes" "org-static"))

(org-publish "org" t )

The full code used in the actual blog can be found at projects.lisp

The variable org-publish-project-alist

This variable holds all the projects to be build, usually a good practice is to split static content from dynamic one. In the code above, "org-notes" are the actual blog posts, while "org-static" publish, as its name says, all the static content.

Compatibility with Hugo

As I have more than 500 posts written since I started blogging (First on blogger, then self-hosted Wordpress, Jekyll, Hugo…) and it was a high time investment trying to migrate all of them without breaking links and style, I decided to leave all of them as they were on Hugo.

I copied the static files generated by hugo to a folder called static/hugo. But org-mode does nothing with this content, since I do not want it to be handled by it. I will elaborate more on this later.

Enable privacy aware commenting system

As I want to leave disqus behind, I have found an usefull Github bot called utterance/utterances that enable users to post comments on every page of the blog, go to the end of the pages and try it out!. The only thing required is a github account, but since this is a tech blog, hopefully almost all readers have one.

Set up a Github Action for automated deployments

At this point I have a ready to publish project. The next step is to automate the deployment so every time I push to elbaulp/elbaulp.github.io a github action runs and deploy the content to Github Pages.

Write a custom Github Action

In order to write a github actions one needs to create a file called action.yml at the root of the repository. This one in particular is very simple, since it only specify that it will execute docker using a Dokerfile:

name: 'Build Blog'
description: 'Build an org-mode project and publish also old hugo blog content'
  using: 'docker'
  image: 'Dockerfile'

Write a Dockerfile

The Dockerfile I've used is based on alpine-emacs and it is also very simple:

  • Installs npm and html-minifier-terser
  • Copy the necessary files with the org-mode project
  • Copy some org-templates.
  • Copy the old version of the blog, build with Hugo
  • Execute the following script:
 1: #!/bin/sh
 5: function print_info() {
 6:     echo -e "\e[36mINFO: ${1}\e[m"
 7: }
 9: emacs --batch --no-init-file --load ./projects.lisp --funcall toggle-debug-on-error
11: html-minifier-terser --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-script-type-attributes --remove-tag-whitespace --use-short-doctype --minify-css true --minify-js true --input-dir ${PUBLIC_DIR} --output-dir ${PUBLIC_DIR} --file-ext html
13: html-minifier-terser --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-script-type-attributes --remove-tag-whitespace --use-short-doctype --minify-css true --minify-js true --input-dir ${PUBLIC_DIR} --output-dir ${PUBLIC_DIR} --file-ext css
15: mv ${GITHUB_WORKSPACE}/static/hugo/* ${PUBLIC_DIR}/
16: mv ${GITHUB_WORKSPACE}/static/googlee20ada3c6370fb77.html ${PUBLIC_DIR}/

emacs --batch --no-init-file --load ./projects.lisp --funcall toggle-debug-on-error runs emacs on batch mode to execute the project.lisp file and generate the blog, then it copies the old version of the blog in Hugo and minimize the contents.


And that is basically it, now everytime I publish to the repository the blog is automatically rebuild.

Future Improvements

  • [ ] Generate a proper rss feed
  • [ ] Handle categories and tags
  • [ ] Enable info-js
  • [ ] Improve semantic content tags
  • [ ] Improve css

Useful resources

Author: Alejandro Alcalde

Date: 2021-02-27 Sat 00:00

Emacs 26.3 (Org mode 9.3.6)