Hugo continues to make occasional splashes on the front page of Hacker News, and like many others who were a little tired of why Jekyll took so long to render even small pages, I took a leap, and would like to share some of my experiences doing so.

The first caveat I should mention before you read ANY further. The biggest downside of Hugo, in my opinion, is that it does not come batteries included with regards to SASS processing. All the blogs that mention how blazingly fast it is(and they are right) don’t mention this fact. IMHO, writing css in the modern day always involves a css preprocessor. Fortunately, SASS compilation handled by many build systems, and I will share my setup at the end of this post.

I didn’t have much luck with the hugo import jekyll command, it just ended up creating empty directories in the new project.

Inspired by https://thatthinginswift.com, here are some basic translations that might help someone migrating from Jekyll. All entries are listed as Jekyll => Hugo.

Helpful functions

{{ page.title | relative_url }} => {{ .Title | relURL }}
{{ page.description | escape }} => {{ .Description | safeHTML }}
{{ page.date | date: '%B %d, %Y' }} => {{ .Date | dateFormat "January 2, 2006" }}

Page titles are simply passed in with the root context, so no reference to page is necessary.

Rendering list of items

# Jekyll - /blog.html
{% for post in site.posts %}
  <li>
      <h2 class="post-title-home">
        {{ post.title | escape }}
      </h2>
    {{ post.content }}
  </li>
{% endfor %}

# Hugo - /layouts/blog/list.html
{{ range .Data.Pages }}
    <h2 class="post-title-home">
      {{ .Title | safeHTML }}
    </h2>
  {{ .Render "li"}}
{{ end }}

A couple of things to note here:

  • The .Data.Pages variable is populated automatically depeding on which section you are part of.
  • “li” is a template, that lives in layouts/blog/li.html, which tells a page how to render.

For more, see:

Template inheritance


# Jekyll

# /_layouts/default.html
<html>
  {% include head.html %}
  <body>
    {{ content }}
  </body>
</html>

# /_includes/head.html
<meta>...</meta>
<meta>...</meta>
<meta>...</meta>

# /_layouts/post.html
<div>
  <h1> {{ page.title }} </h1>
  {{ content }}
</div>

# Hugo

# /layouts can be substituted for /themes/themename, see documentation below.

# /layouts/default/baseof.html
<html>
  {{ partial "head.html" . }}
  {%  head.html %}
  <body>
    {{ block "main" . }}
    {{ end }}
  </body>
</html>

# /layouts/partials/head.html
<meta>...</meta>
<meta>...</meta>
<meta>...</meta>

# /layouts/post/single.html
{{ define "main" }}
<div>
  <h1> {{ .Title }} </h1>
  {{ .Content }}
</div>
{{ end }}

SASS compilation

My old jekyll theme heavily used Bootstrap, so I needed a way to compile sass files. I ended up hacking an npm script to do this:

{
  "name": "brightredchilli-website",
  "version": "0.0.1",
  "description": "Preprocessing code for a hugo site",
  "main": "index.js",
  "scripts": {
    "css:build": "node-sass --source-map true --output-style compressed './themes/brightredchilli/sass/main.scss' --glob -o ./themes/brightredchilli/static/css/",
    "css:watch": "onchange './themes/brightredchilli/sass/' -- npm run css:build",
    "build": "npm run css:build",
    "prewatch": "npm run build",
    "watch": "parallelshell 'npm run css:watch' 'hugo server --buildDrafts --verbose'",
    "start": "npm run watch",
    "deploy": "hugo --baseURL='https://www.yingquantan.com'"
  },
  "author": "Ying",
  "license": "MIT",
  "devDependencies": {
    "node-sass": "^4.7.2",
    "onchange": "^1.1.0",
    "parallelshell": "^3.0.2",
  },
  "dependencies": {}
}

The relevant part is the fact that I use a theme called brightredchilli, and put my sass files inside of the sass directory. Note that I don’t use static/sass, because that would cause hugo to copy the files over to the publish directory, which I don’t want. The ignoreFiles directive in config.yml didn’t seem to work for me.

There are scripts that watch for changes, and node-sass compiles the changes and puts it into the static/css directory. Note in the watch script I start a hugo server with verbose flags.

Another gotcha was that the hugo server serves pages from memory. This means that there is no reference on disk you can use to inspect the layout and output of the content, whether or not files copied over successfully, etc. I found myself periodically just running the hugo command generate the site to publish directory(defaults to /publish).

I found this reading extemely helpful for the migration process - it goes about describing how to create a minimal theme. The process really helps someone learn about how Hugo renders content.

I ended up killing a day doing this, and not everything is migrated properly over, but I’m glad I did it.