How to Create a Static Site with Hugo + Gitlab Pages

What’s a Static Site Generator

Hugo is an awesome static site generator (SSG). What’s a static site generator? What’s even a static site? Well a static site is basically a site without a database behind it. When a web request comes in, instead of the web server making a database query to populate some HTML templates and build HTML files to return, the web server just serves some pre-built HTML files that are already sitting on the web server.

This turns out to have a lot of benefits. But I’ll pretty much avoid going into all that and just skip to the steps to create a website on Hugo and then host it (for free) on Gitlab-pages.

Getting started.

The Hugo documentation is particularly good. Almost everything I lay out below is taken from the quickstart section or the host on Gitlab section.

If you don’t have Hugo installed, you can install it with

$ snap install hugo --channel=extended

Create a new (empty) site and git repo:

~/$ hugo new site modelIdiot
~/$ cd modelIdiot
~/modelIdiot$ git init

We now have the (empty) shell of a site built with Hugo:

~/modelIdiot$ tree -I themes
.
├── archetypes
│   └── default.md
├── config.toml
├── content
├── data
├── layouts
├── static
├── themes

Installing a Theme

Next you’ll want to install a theme. You can browse & choose a theme from the themes gallery. Originally I chose Hyde, but now I’m changing it to hyde-hyde, so that’s what we’ll use here. All the theme sites will give you a git clone and/or git submodule add command to download/install the theme to your Hugo site.

If you go the git clone route, you should probably hose any .git* files in the downloaded theme subdirectory to prevent any weirdness with the parent/project git directory.

If you want to monitor the theme’s repo for potential updates to pull into your theme in the future, you should go the git submodule route. But then if you want to make any changes of your own to any of the files in the theme/subdir, you have to deal with the wackness of git submodules.

There’s probably a use case for git submodules where the benefits are worth the moderate annoyance of dealing with them, but (at least for me) this is not it; I’d rather just basically vendor my theme directory. .git gets the hose again.

~/modelIdiot$ git clone https://github.com/htr3n/hyde-hyde.git themes/hyde-hyde

~/modelIdiot$ ls -a themes/hyde-hyde/

.   archetypes  CHANGELOG.md  .git         images   LICENSE.md  resources  theme.toml
..  assets      exampleSite   .gitmodules  layouts  README.md   static

~/modelIdiot$ rm -rf themes/hyde-hyde/.git*

Starting from the Theme’s Example Site

Now the official Hugo & theme docs suggest updating the base directory’s config.toml to include your themename (in my case adding theme = "hyde-hyde") and then adding content pages. I diverge slightly; I prefer to first copy over all the files from a theme’s exampleSite/ subdir– including content/page structure and config file. Then I swap in my own content into that structure. After all, I chose this theme because I liked the example site it linked to.

~/modelIdiot$ mv themes/hyde-hyde/exampleSite/* .

So we’ve now significantly expanded our site’s config.toml file from what came with hugo new site [sitename] out of the box:

baseURL = "http://example.org/"
languageCode = "en-us"
title = "My New Hugo Site"

to:

## Basic Configuration
baseurl = "https://example.com/"
languageCode = "en"

title = "Title"
theme = "hyde-hyde"

## Hugo Built-in Features
# disqusShortname = "your-disqus-shortname"
# googleAnalytics = "your-google-analytics-id"
# enableRobotsTXT = true

# summarylength = 30

#paginate = 5

## Site Settings
[params]
    author = "Author"
    title = "Title"
    # description = "..."
    authorimage = "/img/hugo.png"
    dateformat = "Jan 2, 2006"

    # sidebar, copyright & license
    copyright = "htr3n"
    license = "CC BY-SA 4.0"
    licenseURL = "https://creativecommons.org/licenses/by-sa/4.0"
    showBuiltWith = true

    # https://highlightjs.org
    highlightjs = true
    highlightjsstyle = "github"
    
    # please choose either GraphComment or Disqus or Utterances
    #GraphCommentId = "..."
    #UtterancesRepo = "..." # https://utteranc.es/
    #UtterancesIssueTerm = "..." # pathname, url, title, og:title
    #UtterancesTheme = "..." # github-light or github-dark

    # Table of contents
    #toc = none, "hugo", or "tocbot"

## Social Accounts
[params.social]
    github = "<username>"
    instagram = "<username>"
    xing = "<username>"
    linkedin = "<username>"
    twitter = "<username>"
    facebook = "<username>"
    stackoverflow = "<username>"
    telegram = "<username>"
    email = "your-email@example.com"
    # gravatar = "your-email@example.com"

## Main Menu
[[menu.main]]
    name = "Posts"
    weight = 100
    identifier = "posts"
    url = "/posts/"
[[menu.main]]
    name = "Portfolio"
    identifier = "portfolio"
    weight = 200
    url = "/portfolio/"
[[menu.main]]
    name = "About"
    identifier = "about"
    weight = 300
    url = "/about/"

In addition, we now have some site content (ignoring the theme subdir because it’s so huge):

~/modelIdiot$ tree -I themes
.
├── archetypes
│   └── default.md
├── config.toml
├── content
│   ├── about.md
│   ├── portfolio
│   │   ├── dera.md
│   │   ├── dera.png
│   │   ├── hyde-hyde.md
│   │   ├── hyde-hyde.png
│   │   ├── _index.md
│   │   ├── laramod.md
│   │   └── laramod.png
│   └── posts
│       ├── creating-a-new-theme.md
│       ├── goisforlovers.md
│       ├── hugoisforlovers.md
│       └── migrate-from-jekyll.md
├── data
├── layouts
└── static
    └── img
        └── hugo.png

However, you’ll notice there are no html files. Hugo is a templating engine; we write pages in markdown, specify configuration parameters and layouts. Then the hugo command builds the html files which will compose our actual site, and stores them in a public/ subdirectory. Since that means everything outside of public/ is our actual source code that generates what’s in public/, we’ll drop public/ in a .gitignore file to keep it out of our version-controlled code repo.

~/modelIdiot$ echo "public/" >> .gitignore
~/modelIdiot$ hugo

Output:

                   | EN  
-------------------+-----
  Pages            | 47  
  Paginator pages  |  0  
  Non-page files   |  3  
  Static files     | 20  
  Processed images |  0  
  Aliases          |  1  
  Sitemaps         |  1  
  Cleaned          |  0  

Total in 118 ms

Before swapping in our own content (and customizing our config.toml), we can first deploy this site locally to make sure it looks as expected and we haven’t messed anything up yet. It should pretty much match the hyde-hyde theme’s example site online:

~/modelIdiot$ hugo serve

Clicking on the resulting localhost link for our locally built site, and comparing to the theme’s example site we see:

Side-by-Side Example Site (locally built/hosted on right)
Figure 1. Side-by-Side Example Site (locally built/hosted on right)

Customizing our site

So they’re not exactly the same, but we’ve got a locally built site that looks pretty good to me. Time to start swapping in our own content.

First steps: Editing the site’s config

The first thing I’ll customize in a new Hugo site is generally to update the site’s config file (config.toml). As a rule of thumb, this will control plenty of things shown on the homepage - e.g. title, logo image, social links, license, etc. These are often pretty self-explanatory.

After a couple quick updates to the title and authorimage fields in config.toml, our site now looks like this:

After a couple quick updates to config.toml
Figure 2. After a couple quick updates to config.toml

The authorimage field in the toml was originally set to /img/hugo.png, which referenced an image located at static/img/hugo.png. So before updating that path for my logo image, I dropped my own logo image in static/img as well.

Then I:

  • removed the [[menu.main]] block for name= "Portfolio" since I don’t have a portfolio section here.
  • swapped in my own usernames for stackoverflow/gitlab/github/linkedin, so those icons would link to my own accounts.
    • Note: for stackoverflow, replacing “username” with my username “MaxPower” led to a dead-link. Using my user-id (1870832) correctly linked to my account, but to the ‘activity’ tab. To link to the ‘profile’ tab as I wanted, the final line I used in my config.toml was: stackoverflow = "1870832/max-power?tab=profile". I found that string by just manually navigating to my stackoverflow profile, and then taking the URL, without the https://stackoverflow.com/users/ at the front.

Swapping in our own content

We can take a look at the content section of the example site we’re starting with:

~/modelIdiot$ tree content

content
├── about.md
├── portfolio
│   ├── dera.md
│   ├── dera.png
│   ├── hyde-hyde.md
│   ├── hyde-hyde.png
│   ├── _index.md
│   ├── laramod.md
│   └── laramod.png
└── posts
    ├── creating-a-new-theme.md
    ├── goisforlovers.md
    ├── hugoisforlovers.md
    ├── Launching a Hugo Site for Free on Gitlab Pages.md
    └── migrate-from-jekyll.md

Now I’ll just delete (rm -rf) the portfolio subdirectory, update the about.md with my own content, and swap out the posts in content/posts with my own markdown blog posts.

Two quick notes:

  1. While Hugo’s input is markdown files, generally for a code-heavy post, I’ll draft the blog post as a jupyter notebook and then nbconvert it to a markdown file.
  2. A post in Hugo needs some metadata up top, which Hugo calls “front matter.” So after nbconverting a jupyter notebook (or drafting a fresh markdown file), we have to add the front matter metadata before building the site with Hugo.

We can see an example of this front matter in one of the posts that came with the example site:

~/modelIdiot$ head content/posts/creating-a-new-theme.md 
---
author: "Michael Henderson"
date: 2014-09-28
linktitle: Creating a New Theme
title: Creating a New Theme
categories: [ "Development", "hugo" ]
tags: ["hugo", "theme", "html", "css"]
weight: 10
---

These metadata fields are accessible as page-specific variables by the Hugo templating code (e.g. the layout files that come with any theme) to organize how pages on the site are organized.

There are many different types of metadata you can include in your Hugo front-matter, for more info see the applicable documentation page

After replacing the example site content files with my own, here’s what my content directory looks like now

~/modelIdiot$ tree content
content
├── about.md
└── posts
    ├── blogging_from_jup_notebooks.md
    ├── Exploring the MNIST Digits Dataset.md
    ├── Launching a Hugo Site for Free on Gitlab Pages.md
    ├── Multiprocessing with Pandas.md
    ├── Pandas Dont Apply _ Vectorize.md
    └── Pandas_View_vs_Copy.md

Now all that’s left to do is…

Deploying the site to Gitlab Pages

Deploying a Hugo site via gitlab pages is incredibly easy>

  1. add this very short and sweet yaml file to your site’s root folder
  2. commit and push some changes on your master branch to your gitlab repo