Featured Image

Intro Link to heading

This post describes how to create and deploy a website like this one. This website was created with Hugo, which is a static site generator. More specifically what that means is that Hugo generates static HTML pages from Markdown content, with styling managed by its inbuilt theme and templating system.

I’ve managed many website over the years. From the early years of static self-hosted websites. To more dynamic websites using PHP and Perl. In later years it’s been fully dynamic websites using JavaScript and Node.js. These websites were often fully fledged CMS websites with a database, admin interface and built in WYSIWYG editor. For a small tech blog like this one such setups are too complex to manage and an overkill when it comes to features. So when I wanted to create a new website I wanted something really simple. Hugo was a perfect choice.

Prerequisites Link to heading

The Hugo CLI should be installed on your computer.

Create your own website Link to heading

Hugo based websites have a very straight forward and code driven setup. Since such a website is based on Markdown content, Markup templates and styling it is easy to version in a Git repository. In its simplest form a website consists of a configuration file (TOML or YAML), a /content/ folder containing the markdown pages, and a theme in the /themes/ folder. The theme contains its own configuration file, markup templates, stylesheets and also content types known as archetypes.

Using the Hugo CLI Link to heading

1. Create a new Hugo website Link to heading

hugo new site my-new-website

2. Change to newly created website folder Link to heading

cd my-new-website

3. Create a new Hugo theme Link to heading

hugo new theme my-new-theme

4. Modify Hugo configuration file Link to heading

Open the /hugo.toml configuration file in your favorite text editor or IDE.

title = "My New Website"
baseURL = "https://my-new-website.github.com"
languageCode = "en"
theme = "my-new-theme"

The title is used throughout the website, while theme is the folder name within the /themes/ folder that contains the active theme for the website.

5. Create new content Link to heading

hugo new content index.md

Add content to the newly created Markdown file /content/index.md.

+++
title = "My New Post"
date = 2024-01-01T12:00:00+00:00
draft = false
+++

# My New Post
This is my new post.

6. Make website into a Git repository Link to heading

touch .gitignore
echo "/public/" >> .gitignore
echo "/resources/_gen/" >> .gitignore
echo "/.hugo_build.lock" >> .gitignore
git init
git add -A
git commit -m "Initial commit"

7. Start Hugo server Link to heading

With this in place your website structure should look like this. The Hugo CLI creates several empty folders which have been removed in the folder view below.

▼ my-new-website
    ▼ content
        index.md
    ▼ themes
        ▼ my-new-theme
            ▼ archetypes
                default.md
            ▼ layouts
                ▼ _default
                    baseof.html
                    list.html
                    single.html
                ⯈ partials
                404.html
                index.html
            theme.toml
    hugo.toml

Run the Hugo server:

hugo server

Open the website http://localhost:1313 in your browser.

new-website

The default theme generated by Hugo does not offer any out-of-the-box styling, so the webpage looks very basic. We will apply improved styling and layout in the following sections.

Customize your website Link to heading

First, to get a better looking website we will add a community developed theme. After that we will add new content to the website. Then, based on the theme we will apply custom templates and styling to get the website looking like we want.

Add theme Link to heading

We will choose the Coder theme for our website. The theme is hosted in a Git repository, so we will add it as a Git Submodule into our website’s Git repository.

1. Add theme as Git Submodule Link to heading

git submodule add https://github.com/luizdepra/hugo-coder.git themes/hugo-coder

2. Update the Hugo config file Link to heading

title = "My New Website"
baseURL = "https://example.org"
languageCode = "en"
theme = "hugo-coder"

[params]
author = "John Doe"
description = "My New Website"
keywords = "developer,personal"
info = ["Deveoper", "Human"]

The author, description and keywords parameters are used in their respective HTML <meta ... /> tags in the page header. The author and info parameters are used in the body of the website home page.

With the Coder theme applied the website now looks like this:

coder-theme

It looks somewhat better than with the default theme. In the next sections we will customize the content types, templates and styling of the theme to make the website look event better.

Add content types Link to heading

Hugo websites have the possibility of defining different content types. Content types makes it possible to group and style content differently. A food and cooking website might have a recipes content type, and a photo website might have an albums content type. In Hugo, content types are called archetypes.

The Coder theme is meant for blogging websites, so it comes with a posts.md archetype. If we look into the /themes/hugo-coder/archetypes/ folder we see that content types are just Markdown files named for the type that they represent. The default.md file is for content that doesn’t have a specific type.

If we want to add any additional content types add an *.md archetype to the /archetypes/ folder. The archetype file contents will be used as a blueprint when creating new content of that type. E.g.

+++
title = ""
description = ""
date = {{ .Date }}
draft = true
+++

Add content Link to heading

As we are creating a blog, let’s create a new blog post for our website.

hugo new content posts/this-is-my-first-blog-post.md

Let’s add some content to our new blog post.

+++
title = "My First Blog Post"
description = "This is my fist blog post"
date = 2024-01-01T12:00:00+00:00
draft = false
+++

# My First Blog Post
Welcome to my first blog post!

With Hugo still running it should have automatically generated a new HTML webpage from the Markdown content we just added. But if we refresh the browser it seems like nothing has changed. The home page looks the same. Thought if we manually enter the address to the blog post we get the webpage: http://localhost:1313/posts/this-is-my-first-blog-post.

In the next sections we will see how we can change the look and feel of the website.

Add custom templates Link to heading

To customize how a Hugo website looks it is possible to simply make changes to the theme. But since we used a community supplied theme changing it would make it cumbersome to update the theme if the theme author patches or improves the theme. We want to leave the theme untouched, so what can we do instead?

We can mirror the file and folder structure of the theme into the root folder of our website in order to override individual files of the theme. Templates in the root folders takes precedence over templates in the theme folders.

First we want to change the home page. Instead of showing information about the author it should show a list of the most recent blog posts. If we look at the contents of the root template /themes/hugo-coder/layouts/index.html we see that it refers to the home.html partial template.

{{ define "content" }}
  {{ partial "home.html" . }}
{{ end }}

Lets’ create a partial template in root folder /layouts/partials/home.html with the following HTML.

{{ with .GetPage "posts" }}
<section class="container">
    <h1>Latest posts</h1>
    <ul>
        {{ range last 10 .Pages }}
        <li>
            <span><a href="{{ .RelPermalink }}">{{ .Title }}</a></span>
            <span> - </span>
            <span>{{ .Date | time.Format (.Site.Params.dateFormat | default "January 2, 2006" ) }}</span>
        </li>
        {{ end }}
    </ul>
</section>
{{ end }}

If we now refresh the browser we can see that we get a list of blog posts on the home page:

coder-theme

Apply custom styling Link to heading

Adding custom stylesheets to the Coder theme is very easy. Many of the themes have equally easy configuration for additional stylesheets. All you have to do is add the name of the custom stylesheet to the [params] section of the hugo.toml website configuration file.

[params]
customCSS = ["css/custom.css"]

After that just create a custom.css file in the /assets/css/ folder and start adding or overriding styling.

Deploy your website to GitHub Pages Link to heading

Once we have a website we are satisfied with we want to publish it on the Internet. GitHub Pages is a free and easy way to host a website. Each GitHub user and organization has the option to host a website in GitHub Pages. If your username is my-username then your corresponding GitHub Page will be my-username.github.io.

Add GitHub Action Link to heading

To publish to GitHub Pages we will use GitHub Action. We create a workflow file /.github/workflows/hugo.yaml with the following contents:

# Workflow for building and deploying a Hugo site to GitHub Pages
name: Deploy Hugo site to GitHub Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches:
      - main
  # Runs on a schedule once a month
  schedule:
    - cron: "0 0 1 * *"

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: "pages"
  cancel-in-progress: false

# Default to bash
defaults:
  run:
    shell: bash

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: 0.136.2
    steps:
      - name: Install Hugo CLI
        run: |
          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
          && sudo dpkg -i ${{ runner.temp }}/hugo.deb                    
      - name: Install Dart Sass
        run: sudo snap install dart-sass
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive
          fetch-depth: 0
      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v5
      - name: Install Node.js dependencies
        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
      - name: Build with Hugo
        env:
          # For maximum backward compatibility with Hugo modules
          HUGO_ENVIRONMENT: production
          HUGO_ENV: production
        run: |
          hugo \
            --gc \
            --minify \
            --baseURL "${{ steps.pages.outputs.base_url }}/"                    
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./public

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

The workflow has two jobs. The first one is the build job that installs the Hugo CLI, then uses that to generate the website contents and build it into a deployable artifact. The second one is the deploy job that deploys the artifact to GitHub Pages.

Commit and push to GitHub Link to heading

So with all this in place we will commit and push the contents to GitHub so that the GitHub Action will run.

git add -A
git commit -m "Initial Hugo site"
git remote add origin git@github.com:my-username/my-new-website.git
git push --set-upstream origin main

If you navigate to the Actions tab in your GitHub repository you should see the jobs running. Once those jobs have completed successfully the website should be live on my-username.github.io.

coder-theme