Porting Jekyll to Hugo

Importing content is always the bane of moving systems.

Now one of the things Hugo has is a cool command called $ hugo import jekyll – and yes, it does exactly what you think it does. This was interesting to me since I wondered if I could switch my library from Jekyll to Hugo and yes, yes I can.

The site I have on Jekyll has a few more posts than the first one I built on Hugo. This one has around 1500 pages. Excited, I ran the command:

Importing...
Congratulations! 0 posts imported!

That’s not right. I spent a few hours trying to run it in various permutations, but I was never able to get it to run right. Thankfully, I didn’t need to since I could just copy my content over. There was no translation needed, as I was already using yaml headers in my .md files so that actually was a pretty painless transition.

The theme on the other hand… Well. I had a complex site with data files and weird junk being thrown around. That was just learning how to translate for-loops for ranges, with some where clauses tossed in.

Was it hard? Yes. But that’s not what you’re really asking.

Was it worth it?

Excuse me while I whip this out:

Change detected, rebuilding site
2015-12-11 21:42 -0800
0 draft content
0 future content
1479 pages created
0 paginator pages created
16 categories created
66 tags created
in 4611 ms

Yes. The site builds faster, which is a win. It also creates tags and categories that can automatically cross link. I can make ‘sections’ without having to define them in my config file, just by making folders. I can spin up templates quickly. I have a little more redundancy than I’d like, but as I master GoLang, this becomes less and less.

Build time ranges from 11737 ms to 2116 ms. Milliseconds. They were into the minutes with Jekyll, even though Jekyll 3 was notably faster than version 2. It was still taking me about 2 minutes to rebuild the Jekyll site, even with incremental builds working. Plus I had to build and rsync every time, in addition to Git pushing. Now I just Git and done.

Oh and JSON works remotely out of the box. I just have to get better at my range calls now.

What’s Different?

Making a ‘static front page’ with Hugo is harder. In fact there’s still an open trac ticket on it. I was able to use an idea I already had, a custom post param called “type” for tagging ‘index’ posts in the news pages so I didn’t get a list of my indexes when I wanted to list all news from 2002. I leveraged that and tagged my primary index ‘mainindex’ and put a quick check in my index.html in my theme:

{{ range .Data.Pages }}
    {{if eq .Type "mainindex" }} 
        {{ partial "content.html" . }}
    {{end}}
{{ end }}

Instead of doing a normal loop where, like a blog it shows me posts in reverse order, it just showed that specific content.

But…

If there’s a file in that Section with the type of ‘Index’ then it shows that. Of course… While index.md doesn’t get used in the main content folder, it does in Sections. If I have a file named index.md in a section, that makes index.html like I expected. Since I still wanted lists in the majority of cases, I tweaked the content of my default list file (list.html) so that I could always show a ‘main’ file:

    {{ range .Data.Pages.ByTitle }}
        {{ if eq .Type "index" }}
            {{ .Content }}
        {{ end }}
    {{ end }}

    <ul>
    {{ range .Data.Pages.ByTitle }}
        {{ if ne .Type "index" }}
            <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
        {{ end }}
    {{ end }}
    </ul>

Then I renamed all the index.md files in the Sections to main.md but this created another issue. Now I had two URLs that worked. I could have put this into the template for the section to avoid this, but I want to separate content and theme as much as humanly possible. WordPress habits. For now, I’ve left this alone. If someone is clever enough to go to a non-linked, non-sitemaped page instead of the index, I think my SEO will live. It’s more than I wanted to play around with loops.

Speaking of the loops and lists, getting a list of a ‘sub section’ is not currently possible either. Rather, they do work, but there’s no default templates for sub-sections. This was a small problem since I structured my site like this:

.
└── content
    ├── news
    |   ├── index.md          // /news/
    |   ├── 2001
    |   |   ├── loch-ness.md  // /news/2001/loch-ness/
    |   |   └── monster.md    // /news/2001/monster/
    |   └── 2002
    |       ├── nessie.md     // /news/2002/nessie/
    |       └── spock.md      // /news/2002/spock/
    └── posts

Now, I don’t need a custom template for each sub-section, but I did want to use my custom list template for each sub-section. To work around this, I made a template in /layout/section/news.html that had this:

	{{ range .Data.Pages.ByTitle }}
		{{ if and (eq .Type "index") (ne .Params.topic "index") }}
			{{ .Content }}
		{{ end }}
	{{ end }}

I know this is weird. The Type of index means “I am the main index file of the section” while the topic index means “I am an indexing file and not a normal news article. This gives me my main index of /news/ giving me a custom content page with information like I wanted, using the main.md format I use in all sections.

Then in each sub-section, I use a very simple index.md file:

---
title: News Articles (1992)
author: Mika Epstein
layout: news
topic: index
date: 1992-01-01
permalink: /news/1992/
categories: ["News"]
tags: ["1992"]
---

{{< news >}}

That weird bit in the content is a shortcode ala Hugo, which calls the file /layouts/shortcode/news.html that has this:

{{ $date := $.Page.Date.Format "2006" }}

<table class="infobox">

    <thead><tr>
        <th>Date</th>
        <th>Source</th>
    </tr></thead>

    <tbody>
    {{ range where $.Page.Site.Pages.ByDate "Section" "news" }}
        {{ $thisdate := .Date.Format "2006" }}

        {{ if and (eq $date $thisdate) (ne .Params.topic "index") }}

            <tr>
                <td>{{ .Date.Format "01 January" }}</td>
                <td><a href="{{ .Permalink }}">{{ .Title }}</a></td>
            </tr>

        {{ end }}
    {{ end }}
    </tbody>
</table>

That spits out a table of all the pages in that section by date.

What’s Next?

It took a lot to get here, or so it may seem. This took me two nights to work out. That’s it. So now I have to figure out the next steps that have vexed me:

  1. How do I properly mimic a for-loop with range?
  2. Can I make a responsive gallery out of Hugo?
  3. Can I power a Hugo site with WordPress JSON?

And that’s a whole ‘nother story.