As a developer, I’ve had plenty of experience setting up sites for clients, but when it comes to my own site, I often can’t be bothered. WordPress is too much of mess, Drupal is too heavy and working with Laravel or Symfony usually ends up being an exercise in the “perfect” rather than a functional website. For a few years now, I’ve had a basic website with minimal information, built on Slim Framework - a lightweight version of Laravel. It was the replacement for a failed Drupal site, if memory serves.

In any case, I would have to set up a server and maintain it, with all the work that entails. I could also find some hosted solution which would take care of updating and maintaining it, but in all honesty, would I really be able to look myself in my developer eyes and say: “I’m a pro”, if I did that? So now I have a new site; what changed?

Hugo

About a month ago, I was talking to MortenDK, and he was going on about the wonders of clean CSS and flat html pages with a tech stack so simple, even the IT cavemen of 1990 would be able to figure it out. He told me about a few of the static file “CMS’s” he’d been looking at, and I decided to take a look too.

After some checking around, I found Hugo. Written in Go, with a Markdown processor, a relatively simple theme layer, a sizable list of themes and documentation for days, it seemed perfect.

Comfortable in markdown

I’ve always believed that documentation is best kept close to the code, because it increases the chances of developers actually maintaining it alongside the code. So when I realized Hugo uses markdown for content, I felt right at home. Even more so, because I can focus on writing my content and not actually care about the HTML and CSS too much. It still supports some HTML tags, but most of the time you’re just typing text, putting focus on the content and not the markup. This page was created entirely with Markdown, using the syntax Hugo has added to allow you to structure your content.

This page has the following YAML-like configuration in it:

title: "Hugo: A static site."
date: 2021-04-20T18:30:00+01:00
description: "A static site built on gohugo.io"
menu:
    sidebar:
        name: "Hugo: A new site."
        identifier: static-site-with-gohugo
        parent: tech
        weight: 11
categories: [ "tech", "hugo", "gohugo", "flat file", "website", "static" ]

As you can see, I’ve set the menu, added a description and even some categories to the page. This makes it searchable (using Javascript magic) and also takes care of the seo for me.

Design is not my thing

I was never a design guy. Like everyone else, I have an opinion, but it rarely goes beyond “bike shedding”. So I was happy to find a sizable library of plug and play themes to choose from. I went with Toha, because I like the single page nature of it, while I have this simple posts page to do my writing on.

Adding the theme was simple enough, though it was done using git submodules. This approach is a little 2005 for me, and I certainly hope Hugo will add a dependency management tool at some point. For now, the Toha theme uses dependency management for its CSS and Javascript dependencies.

Adding the theme:

$ git submodule add https://github.com/hugo-toha/toha.git themes/toha

I’m NOT a snowflake

Because I needed a special section with my recommendations, and certainly not because I’m a snowflake, I decided to look into how to override and add templates. Looking at how the YAML and templates were structured in the theme, it was easy to work out how to add a new section. However, writing the template required a little more digging, and this is where having a well documented framework helps. I went over to Hugo’s documentation and jumped to the template section and within a few hours of testing, I was able to create the template I wanted.

# section information
section:
  name: Recommendations
  id: recommendations
  enable: true
  weight: 10
  showOnNavbar: true
  # Can optionally hide the title in sections
  # hideTitle: true

recommendations:
- name: My friend
  company: His company
  recommendation: "His review."

The above YAML was added to the list of sections, and a few recommendations was added. Next I went on to the templates, which were added to the layouts/partials/sections folder, and the layouts/partials/recommendations folder:

/layouts/partials/sections/recommendations.html

{{ $sectionID := replace (lower .section.name) " " "-"  }}
{{ if .section.id }}
{{ $sectionID = .section.id }}
{{ end }}

<div class="container-fluid anchor pb-5 recommendation-section" id="{{ $sectionID }}">
    {{ if not (.section.hideTitle) }}
    <h1 class="text-center">{{ .section.name }}</h1>
    {{ end }}
    <!-- recommendations-holder holds recommendation-entry -->
    <div class="card-deck" id="recommendation-holder">
        {{ range .recommendations }}
        {{ partial "recommendations/recommendation.html" . }}
        {{ end }}
    </div>
</div>

/layouts/partials/recommendations/recommendation.html

<div class="col-xs-12 mb-md-2">
    <div class="card">
        <div class="card-body">
            <blockquote class="mb-0">
                <p class="card-text">"{{ .recommendation | markdownify }}"</p>
                <footer class="blockquote-footer">{{ .name }} at <cite title="Source Title">{{ .company }}</cite></footer>
            </blockquote>
        </div>
    </div>
</div>

Hosting Hugo

Ever since I started working as a developer, hosting has been the root of most of my headaches. Configuring the server, hardening it, updating and maintenance are all tasks requiring a special set of skills. Through the years, I have used Ansible, CHEF, and Puppet to manage server configuration. The servers I’ve worked on have been placed locally (dev setups), on premise physical, virtual servers and, lately, in the cloud (AWS and Azure). I’m not a server guy, and usually I let people with more knowledge and experience than I deal with setting up servers and making sure they are secure. So when it came to hosting my new Hugo site, I would have to set up some EC2 instance with a nginx server and figure out some form of maintenance script. However, it turns out that when you have no need for server-side processing, you have many options for maintenance free hosting. These include, but are not limited to, Netlify, Firebase, GitHub (yes, you can host a website there), and Google Cloud Storage (GCS). I decided to go with something I knew, which was Amazon S3.

Setting up

Setting up an S3 bucket has been documented many times. Amazon has created a great guide themselves here. The tldr; of it is the following:

  1. Create bucket
  2. Make bucket public
  3. Assign access policy
  4. Add files

For the access policy, I simply added the following, which grants everyone rights to get content from the bucket:

{
    "Version": "2012-10-17",
    "Id": "{id}",
    "Statement": [
        {
            "Sid": "{sid}",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::{bucket-id}/*"
        }
    ]
}

Deployment

Next I needed to add the files, but not wanting to do it manually, I installed the aws cli to make use of the s3 sync command. I quickly realized it was advantageous to remove all content from the bucket, before deploying the newly generated site, so I started out with removing the bucket contents. This will mean the site is unavailable for the time it takes to add in the new content, however you can simply use the s3 sync without first removing the content, if you want.

$ aws s3 rm --recursive s3://{bucket-id}
$ aws s3 sync public s3://{bucket-id}

As you can see, I just push all the contents of the public folder to the bucket. This is generated by simply running the hugo command locally in your site.

Continuous integration (CI)

I have yet to look into and set up CI properly, but I have found a pretty good example of how to do it here. In it, a GitHub workflow file is defined as follows:

name: Build and Deploy

on: push

jobs:
  build:
    name: Build and Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install Hugo
        run: |
          HUGO_DOWNLOAD=hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz
          wget https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${HUGO_DOWNLOAD}
          tar xvzf ${HUGO_DOWNLOAD} hugo
          mv hugo $HOME/hugo          
        env:
          HUGO_VERSION: 0.81.0
      - name: Hugo Build
        run: $HOME/hugo -v
      - name: Deploy to S3
        if: github.ref == 'refs/heads/main'
        run: $HOME/hugo -v deploy --maxDeletes -1
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

As far as I can tell, this would be my preferred way of deploying a hugo site to S3. I’ll likely set this up at some point, to test it out, and make my life easier. CI is just an easier way to work with deployments, and it means I simply have to push my content into my repository to deploy it. What could be easier?

Final thoughts

Hugo does everything I need from CMS, without being an actual CMS. I have control over templates, and can create the look and feel I want using custom or contributed themes. I have version control of content and templates through GitHub, and I can even set up automatic deployments with CI. It’s lightning fast, I don’t have to worry about hosting or security, and best of all I don’t have hosting fee’s (so far).

I wouldn’t recommend Hugo to everyone, however. I’m a developer, so I can figure out most of what’s needed myself. I’m not intimidated by a console, and obscure technical documentation only gives me light headaches. I can see a case for writing a getting started guide for content creators to create their content, but if you have little or no technical skills, getting a functioning Hugo site from scratch is likely going to overwhelm you.

I will continue working with Hugo in my spare time, and at some point I might suggest it to clients. For now, it will just be my side project, and I will post updates if anything interesting happens.