My first adventure with Hugo

Hugo looks like a reasonable system for generating static websites. I’ll start working with it and I’ll document the process as I go along. My immediate goal is to set up Hugo in a vanilla configuration and use it to post a new piece of content. The official Hugo documentation covers many usage scenarios. Here I’ll describe one possible path to setting up Hugo and using it for the first time. I hope this walkthrough will be useful to others who are embarking on a Hugo adventure.

My current hosting and content management practices

The current version of my website is a collection of PHP templates that I rolled myself over the years. The production website is hosted on an Ubuntu server at DreamHost, which runs Apache. Locally I use an Ubuntu workstation running Apache.

I host a local copy of the production website. I develop content in separate, non-hosted directories. When a piece of content is complete, I stage it in my local production website, then copy the updated files to the remote production website.

The plan

To retool my website with Hugo, I’ll carry out the following steps.

Install: Download the Hugo package and get it running.
Configure: Choose a simple Hugo theme and do minimal configuration.

Create: Add a new piece of content to my Hugo website.

Style: Make the content look reasonably attractive.

Merge: Figure out how to run Hugo within my existing website installation.

Enjoy: Stage the merged website locally, then upload it to DreamHost and make sure everything works.

What I’d like to see at the end is a new home page that is generated by Hugo and that links to a single post, namely this one. For the time being I won’t link the Hugo pages to any previously existing content. However, the old content has to stay alive at the same URLs as before.

The priority is to get Hugo up and running. Migrating my old content to Hugo is a task for later.

Install

I’m opting to download a precompiled binary of Hugo’s latest release, version 0.15 at this writing.

Make a local directory for Hugo and download the tarball.

$ mkdir ~/hugo
$ cd ~/hugo
$ wget https://github.com/spf13/hugo/releases/download/v0.15/hugo_0.15_linux_amd64.tar.gz

Unpack the tarball and take a look at the resulting directory.

$ tar xf hugo_0.15_linux_amd64.tar.gz
$ ls hugo_0.15_linux_amd64
hugo_0.15_linux_amd64*  LICENSE.md  README.md

The Hugo binary can run in any location. I’ll leave it where I unpacked it and make a symbolic link called /usr/local/bin/hugo.

$ sudo ln -s ~/hugo/hugo_0.15_linux_amd64/hugo_0.15_linux_amd64
/usr/local/bin/hugo

Make sure the symbolic link works and the hugo binary runs.

$ ls -l /usr/local/bin/hugo
lrwxrwxrwx 1 root root 59 Dec 28 09:28 /usr/local/bin/hugo -> /home/mike/hugo/hugo_0.15_linux_amd64/hugo_0.15_linux_amd64*
$ cd ~
$ which hugo
/usr/local/bin/hugo
$ hugo version
Hugo Static Site Generator v0.15 BuildDate: 2015-11-25T09:35:21-05:00

Configure

Make a local directory for my new Hugo website.

$ mkdir ~/ml.hugo

Use the “hugo new site” command to make a skeleton site.

$ cd ~/ml.hugo
$ hugo new site .
$ ls
archetypes/  config.toml  content/  data/  layouts/  static/

Make two pieces of temporary content to test Hugo and try out themes.

$ hugo new about.md
$ hugo new post/today.md
$ ls content
about.md post
$ ls content/post
today.md
$ vim content/about.md
$ vim content/post/today.md

A theme is required to display content. Go to the Hugo theme gallery to browse screenshots.

Pick a theme that requires no images or special content sections to lay out properly. These are the ones that caught my eye:

Cactus, Casper, Cocoa, Crisp, Heather Hugo, Hyde X, Simple A

Instead of downloading themes individually, download all the themes currently available so that I have them locally and can try them out at my leisure.

$ git clone --depth 1 --recursive https://github.com/spf13/hugoThemes.git themes

Run Hugo’s built-in web server. Here I’m specifying the Cactus theme:

$ hugo server --theme=cactus --buildDrafts

Hugo displays several error messages and keeps running. It’s serving the website at http://localhost:1313. The content is there, but it’s unstyled.

Hugo site without configuration file

The theme fails to work because I’m lacking an appropriate configuration file. Each theme expects to see certain things in a file called config.toml, located in the root directory of the Hugo website.

Clicking on a screenshot in the Hugo theme gallery takes us to a page of documentation provided by the theme creator.

The online doc for the Cactus theme tells us what to do:

Take a look inside the exampleSite folder of this theme. You’ll find a file called config.toml.

To use it, copy the config.toml in the root folder of your Hugo site. Feel free to change strings as you like to customize your website.

Let’s do that. Copy the config.toml file to the top-level Hugo directory.

$ pwd
/home/mike/ml.hugo
$ ls themes/cactus
archetypes/   exampleSite/  layouts/    README.md  theme.toml
CHANGELOG.md  images/       LICENSE.md  static/
$ ls themes/cactus/exampleSite
config.toml  content/  static/
$ cp themes/cactus/exampleSite/config.toml .

Start the Hugo server again and check http://localhost:1313/.

$ hugo server --theme=cactus --buildDrafts

There is an immediate improvement with the default configuration file.

Cactus theme with default configuration

My name isn’t Mr. Hugo. Let me edit the config.toml file.

$ vim config.toml

Only a few of the parameters in the Cactus configuration file are useful to me at this point. I’ve stripped down my config.toml file to this:

# Site settings
baseurl = "http://replace-this-with-your-domain.com/"
languageCode = "en-us"
title = "michaellaszlo.com"
theme = "hugo-cactus-theme"

[params]
  name = "Michael Laszlo"
  description = "Michael Laszlo's personal website"
  bio = ""

  # Links
  home = ""
  about = ""
  subscribe = ""
  olderPosts = ""
  newerPosts = ""
  readMore = ""
  copyright = " "
  title404 = ""
  subtitle404 = ""

  # Sharing options and author information in posts
  aboutAuthor = "I'm a full-stack web developer."
  tweet = " "
  share = " "

Note that copyright = "" is distinct from copyright = " ". I’ve found after experimentation that writing an empty string causes a copyright notice to appear at the bottom of the page. With a blank space, the copyright notice is suppressed.

I’ve also discovered that the Twitter and Facebook buttons will shrink if we write tweet = " " and share = " ".

After modifying the configuration, we have to stop the Hugo server with Ctrl-C, restart it, and reload http://localhost:1313/.

On the other hand, when we modify content files, there is no need to restart the Hugo server. It watches for changes in content and rebuilds the site on the fly. We don’t even have to reload the browser.

Here is my home page with the new configuration:

Cactus theme with modified configuration

That’s enough configuration for now. It’s time to add some real content.

Create

The first piece of content I want to add the Hugo website is this very post. I’ve been writing it in plain text. I’ll copy this content as it is to the Hugo website and see what happens.

The directory where I’ve been writing content is separate from the directory containing my Hugo website. The content is in a text file named ~/ml.content/first.hugo.adventure/first.hugo.adventure.txt. I’m going to copy it to a Hugo content directory, ~/ml.hugo/content/post/.

$ cp ~/ml.content/first.hugo.adventure/first.hugo.adventure.txt content/post/first.hugo.adventure.md

I changed the filename extension to .md because that is what Hugo expects for content files. The .md suffix stands for Markdown, which is a popular markup language for writing web content. My file doesn’t contain any markup, though.

Here are the first few paragraphs of the file:

Hugo (http://gohugo.io/overview/introduction/) looks like a good
system for generating static websites. I'll start working with it and
I'll document the process as I go along. My immediate goal is to set
up Hugo in a vanilla configuration and use it to post a new piece of
content. The official Hugo documentation covers many usage scenarios. Here
I'll describe one possible path to setting up Hugo and using it for the
first time. I hope this walkthrough will be useful to others who are
embarking on a Hugo adventure.


My current hosting and content management practices

The current version of my website is a collection of PHP templates that
I rolled myself over the years. The production website is hosted on an
Ubuntu server at DreamHost, which runs Apache. Locally I use an Ubuntu
workstation running Apache.

I host a local copy of the production website. I develop content in
separate, non-hosted directories. When a piece of content is complete,
I stage it in my local production website, then copy the updated files
to the remote production website.


The plan

Immediately after I wrote this plain text file to content/post/first.hugo.adventure.md, the home page of the Hugo website changed:

Home page showing content without front matter

There appear to be two pieces of content now, one of them without a title. When I click on the second instance of “Dec 28”, I see the plain text file interpreted as web content:

Plain text file served as Hugo content

It looks much as it did in vim. The plain text paragraphs have turned into HTML paragraphs. URLs written in text have been made into live links. The post has no title. Its date has been set to January 1 in the year 1.

We have to provide meta-data that specifies things like the title and date of the post. This meta-data is called front matter. It’s a specially formatted block of text that appears at the head of the content file.

Among the several formats that Hugo recognizes for front matter, I’m choosing TOML because it has a short and clear specification.

I have written minimal front matter:

+++
title = "My first adventure with Hugo"
date = "2015-12-28"
+++

In order for it to take effect, front matter must be inserted before any content.

Now the post appears with a title and accurate date:

With front matter but without markup

Style

Much of my post consists of plain paragraphs, but there are headings, links, and other features that call for additional formatting.

To present these elements effectively, I have to mark up the content. Hugo supports a markup language called Markdown. I consulted GitHub’s Markdown primer.

I also found it useful to study a sample page included in the Cactus theme:

$ vim ~/ml.hugo/themes/cactus/exampleSite/content/post/

With the help of these resources, I quickly learned how to mark up my text. Below is a summary of what I needed to know to mark up this article.

Text formatting

Headings can be made by starting a line with several instances of the number sign, #, also known as the octothorpe or hash tag.

If you write a single #, the line is rendered as an <h1> or top-level heading. However, most Hugo themes put the content title in <h1> tags. The title should be the only top-level heading. You should use lower-level headings in the content body. Write ## to make section headings, ### for subsections, and so on.

Here are examples of markup for <h2>, <h3>, and <h4> headings:

## Birds

### Flightless birds

#### The ostrich

The markup is rendered thus:

Birds

Flightless birds

The ostrich

The following demonstrates links, italics, boldface, and block quotes:

[Corn flakes](https://en.wikipedia.org/wiki/Corn_flakes) are *good*.

> So is **oatmeal**.

Corn flakes are good.

So is oatmeal.

This is an unordered list:

- emu
- kiwi
- rhea

In an ordered list, item numbers are assigned automatically:

0. buckle my shoe
0. open the door
0. pick up sticks
  1. buckle my shoe
  2. open the door
  3. pick up sticks

To show a block of code, write three backticks before it and after it:

```
for (pos = 0; pos < s.length; ++pos) {
  console.log(pos, s.charAt(pos));
}
```

Or prepend four spaces at the start of each line:

    for (pos = 0; pos < s.length; ++pos) {
      console.log(pos, s.charAt(pos));
    }

Either way we get the same result:

for (pos = 0; pos < s.length; ++pos) {
  console.log(pos, s.charAt(pos));
}

To write a code fragment inside a paragraph, enclose the code in single backticks:

The `pos` variable is declared at the head of the `display(s)` function.

The pos variable is declared at the head of the display(s) function.

Images

We can embed an image with the following markup.

![Alt text](image source)

If we’re hosting an image ourselves, we have to decide where on the server to store it. I have found it convenient to collect the images for a given post in a directory named for the post and located alongside it. For example, when I’m working on the content file content/post/first.hugo.adventure.md, the images that appear in the post go into content/post/first.hugo.adventure.

When writing the URL of an image file located under the content directory, we omit the prefix content from the pathname. For example, I have an image named world.map.png in the directory content/post/first.hugo.adventure.

$ ls ~/ml.hugo/content/post/first.hugo.adventure/world*
world.map.png

To present the image, I write the following markup.

![Blank world map](/post/first.hugo.adventure/antarctica.1500x750.jpg)

Blank world map

Similarly, when writing an URL for an asset that is in Hugo’s static directory, we omit the prefix static from the URL.

If an image is natively wider than the surrounding content, most themes will shrink the image to make it fit the content width. This can be problematic if the image contains fine details such as text.

The reader can always view an image at its native resolution by right-clicking on it and selecting the menu item that opens the image in a new tab.

We can make it more convenient by wrapping the image in a link, so the user need only left-click to see the image at native resolution:

[![Antarctica](/post/first.hugo.adventure/baked.goods.1500x750.jpg)](/post/first.hugo.adventure/baked.goods.1500x750.jpg)

Antarctica

If we want the click to automatically open a new tab containing the image, we can wrap the image markdown in a plain HTML anchor with the attribute target="_blank":

<a target="_blank" href="/post/first.hugo.adventure/blue.whale.1500x750.jpg">![Baked goods](/post/first.hugo.adventure/blue.whale.1500x750.jpg)</a>

Baked goods

This approach works well for most images. It isn’t ideal if an image has a background of the same color as the surrounding content and the theme doesn’t put a border around images. In such cases, it won’t be clear where the content ends and where the image begins. This is true when a screenshot of a page using the Cactus theme is shown with the Cactus theme:

Cactus theme screenshot shown in Cactus theme

Changing the theme

In using the Cactus theme so far, I have come across some points of friction. Numbered lists in Markdown are rendered without numbers. Inline code formatting doesn’t work. The content column is too narrow to show most code blocks without breaking lines. Headings are padded in such a way that they’re closer to the preceding text than to the text that follows. The primary font family is Helvetica Neue, which is not available on most platforms.

Although Cactus is a good theme in other respects, I have decided that it doesn’t suit me at present. Hugo is a young ecosystem and many themes such as Cactus are in their infancy. I have no doubt that they will grow in power and refinement as development continues.

What can I do right now to launch my Hugo website? I suppose I could modify Cactus until it does everything I want it to. However, it would be more expeditious to switch to a theme that comes closer to meeting my needs.

I am switching to the Casper theme because it gets the basics right. The spacing is sensible, inline code excerpts look good, and everything lays out well in mobile browsers.

Because I downloaded the full set of Hugo themes earlier, I can switch to the Casper theme by stopping the Hugo server and restarting it with a new --theme argument:

$ hugo server --theme=casper --buildDrafts

Now my home page at http://localhost:1313/ looks like this:

Home page after switching to Casper theme

It looks respectable even though I’m using the Cactus configuration file. Much of the configuration is supported by Casper as well, but I can set additional parameters with an appropriate config.toml.

The configuration instructions on Casper’s showcase page can also be found in the README.md file provided with the Casper theme:

$ cd ~/ml.hugo/
$ ls themes/casper
archetypes/  images/   LICENSE.md  static/
data/        layouts/  README.md   theme.toml
$ vim themes/casper/README.md

The theme author links to his personal configuration, which I could use as a starting point. Another option is to copy the simpler config.toml that is provided in the README.md.