Building and Deploying a Blog with Hugo and GitHub Pages

In today's fast-paced digital age, where information is just a Google or ChatGPT search away, setting yourself apart is more important than ever. Blogging is one of the most effective ways to showcase your knowledge, contribute to the community, and build your brand. Think of it as a dynamic extension of your resume, a portfolio that grows and evolves with you. It provides a platform to share ideas, tackle complex problems, and express your viewpoints while delivering immense value to your readers.

Prerequisites

Install Hugo

Hugo is a static site generator built with Go. It's popular for its speed, flexibility, and ease of use. Hugo Extended is a variant of Hugo that supports additional features like Sass/SCSS compilation, image processing, and more. Hugo Extended is Hugo but with some extra capabilities to help manage more complex projects. For more information, see the Hugo website.

Install Hugo Extended using your operating system's package manager. While the following aren't the only ways to install Hugo, they're how I installed it on each operating system I use. For additional options, see the installation section of the Hugo website.

Windows

Install Hugo Extended using the Windows Package Manager.

1winget install Hugo.Hugo.Extended --source winget
1Found Hugo (Extended) [Hugo.Hugo.Extended] Version 0.119.0
2This application is licensed to you by its owner.
3Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
4Successfully verified installer hash
5Extracting archive...
6Successfully extracted archive
7Starting package install...
8Command line alias added: "hugo"
9Successfully installed

macOS

Use the Homebrew package manager to install Hugo Extended.

1brew install hugo
 1==> Downloading https://ghcr.io/v2/homebrew/core/hugo/manifests/0.119.0
 2==> Fetching hugo
 3==> Downloading https://ghcr.io/v2/homebrew/core/hugo/blobs/sha256:6bc80ff59b610e29a2ace40e6f473b3a8e3703c8692374f5fa2ab
 4==> Pouring hugo--0.119.0.arm64_sonoma.bottle.tar.gz
 5==> Caveats
 6zsh completions have been installed to:
 7  /opt/homebrew/share/zsh/site-functions
 8==> Summary
 9🍺  /opt/homebrew/Cellar/hugo/0.119.0: 50 files, 68.3MB
10==> Running `brew cleanup hugo`...
11Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
12Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

Linux

Use the package manager for your distribution to install Hugo Extended. The following example uses the APT package manager to install Hugo Extended on Ubuntu.

1sudo apt install hugo
 1Reading package lists... Done
 2Building dependency tree... Done
 3Reading state information... Done
 4The following NEW packages will be installed:
 5  hugo
 60 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
 7Need to get 11.2 MB of archives.
 8After this operation, 48.3 MB of additional disk space will be used.
 9Get:1 http://ports.ubuntu.com/ubuntu-ports jammy-updates/universe arm64 hugo arm64 0.92.2-1ubuntu0.1 [11.2 MB]
10Fetched 11.2 MB in 1s (8547 kB/s)
11Selecting previously unselected package hugo.
12(Reading database ... 203526 files and directories currently installed.)
13Preparing to unpack .../hugo_0.92.2-1ubuntu0.1_arm64.deb ...
14Unpacking hugo (0.92.2-1ubuntu0.1) ...
15Setting up hugo (0.92.2-1ubuntu0.1) ...
16Processing triggers for man-db (2.10.2-1) ...

Create a Hugo site

Using PowerShell, navigate to the parent directory on your computer's file system where you want to create your Hugo site. The examples in this article use the $HOME/Developer/git directory as the parent path.

1Set-Location -Path $HOME/Developer/git

Create a new Hugo site. The examples in this article use myhugosite for the Hugo site name.

1hugo new site myhugosite
 1Congratulations! Your new Hugo site was created in /Users/mikefrobbins/Developer/git/myhugosite.
 2
 3Just a few more steps...
 4
 51. Change the current directory to /Users/mikefrobbins/Developer/git/myhugosite.
 62. Create or install a theme:
 7   - Create a new theme with the command "hugo new theme <THEMENAME>"
 8   - Install a theme from https://themes.gohugo.io/
 93. Edit hugo.toml, setting the "theme" property to the theme name.
104. Create new content with the command "hugo new content <SECTIONNAME>/<FILENAME>.<FORMAT>".
115. Start the embedded web server with the command "hugo server --buildDrafts".
12
13See documentation at https://gohugo.io/.

Navigate to the root directory of your newly created Hugo site.

1Set-Location -Path $HOME/Developer/git/myhugosite

Create a Git repository

Initialize the site's directory as a Git repository.

1git init
1Initialized empty Git repository in /Users/mikefrobbins/Developer/git/myhugosite/.git/

Add a .gitignore file

1$gitIgnore = @'
2.DS_Store
3.hugo_build.lock
4/public/
5/resources/_gen/
6'@
7Set-Content -Path $HOME/Developer/git/myhugosite/.gitignore -Value $gitIgnore -Force

Add a theme

Adding a theme to a Hugo website can enhance its aesthetics and functionality. Hugo offers various methods for installing and using themes. This article uses Hugo modules to add a theme to the site. See your theme's documentation for guidance on adding a theme to a Hugo site.

Initialize a new Hugo module. This initialization creates a go.mod file in the root of the site. The go.mod file tracks the dependencies of the site and the version of Go that the site uses. Hugo and GitHub Actions use the version of Go when building the site.

1hugo mod init github.com/<github-username>/myhugosite
1go: creating new go.mod: module github.com/mikefrobbins/myhugosite
2go: to add module requirements and sums:
3	go mod tidy

Remove the Go patch version from the go.mod file. This modification is required because GitHub Actions only supports Go specified in Major.Minor format. GitHub Actions builds and deploys the site to GitHub pages and fails if the go.mod file includes the patch version. For more information about Semantic versioning, see semver.org.

1Get-Content -Path ./go.mod -OutVariable content
2$content -replace '([^\.]*\.[^\.]*)\..*', '$1' | Out-File -FilePath ./go.mod -Force
3Get-Content -Path ./go.mod
1module github.com/mikefrobbins/myhugosite
2go 1.21.3
3
4module github.com/mikefrobbins/myhugosite
5go 1.21

Add a theme to the Hugo site. This article uses the Relearn theme for Hugo.

1hugo mod get github.com/McShelby/hugo-theme-relearn
1go: downloading github.com/McShelby/hugo-theme-relearn v0.0.0-20240304200407-d81b4dd6eb9a
2go: added github.com/McShelby/hugo-theme-relearn v0.0.0-20240304200407-d81b4dd6eb9a

Configure Hugo to use the Relearn theme.

1Add-Content -Path hugo.toml -Value "theme = 'github.com/McShelby/hugo-theme-relearn'"

Add content

Create a new post. The following example uses the name my-first-article.md for the new post. The name of the post is the name of the file created in the content/posts directory.

1hugo new content posts/my-first-article.md
1Content "/Users/mikefrobbins/Developer/git/myhugosite/content/posts/my-first-article.md" created

Add content to the newly created my-first-article.md post. You write the content for Hugo posts in Markdown. Markdown is a lightweight markup language that you can use to add formatting elements to plaintext text documents. Title and date are the only metadata elements specified in the following example. See the documentation for your specific theme to determine the supported metadata.

 1$post = @'
 2---
 3title: "My First Article"
 4date: 2023-10-26T06:30:00-05:00
 5---
 6
 7Insert lead paragraph here.
 8
 9> Block quote
10
11**bold**
12
13_italic_
14
15Unordered list
16
17- first
18- second
19- third
20
21Ordered list
22
231. first
241. second
251. third
26
27Inline code `Get-Help`.
28
29Cmdlet name `Get-Help`.
30
31Parameter name **ParameterName**.
32
33Parameter value `$true`.
34'@
35Set-Content -Path $HOME/Developer/git/myhugosite/content/posts/my-first-article.md -Value $post

Build the site locally

Start the Hugo server locally on your computer. Hugo serves the site locally so that you can view a live preview in your web browser. The Hugo server watches for changes to the site and automatically rebuilds it when changes are detected.

1hugo server
 1Watching for changes in /Users/mikefrobbins/Developer/git/myhugosite/{archetypes,content,layouts,static}
 2Watching for config changes in /Users/mikefrobbins/Developer/git/myhugosite/config/_default, /Users/mikefrobbins/Developer/git/myhugosite/config/_default/menus, /Users/mikefrobbins/Developer/git/myhugosite/go.mod
 3Start building sites …
 4hugo v0.119.0-5fed9c591b694f314e5939548e11cc3dcb79a79c+extended darwin/arm64 BuildDate=2024-03-07T13:14:42Z VendorInfo=brew
 5
 6                   |  EN
 7-------------------+-------
 8  Pages            |   10
 9  Paginator pages  |    0
10  Non-page files   |    0
11  Static files     |  204
12  Processed images |    0
13  Aliases          |    0
14  Sitemaps         |    1
15  Cleaned          |    0
16
17Built in 101 ms
18Environment: "development"
19Serving pages from memory
20Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
21Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
22Press Ctrl+C to stop

Consider starting hugo server as a background job because otherwise, it ties up your PowerShell prompt or session.

1Start-Job -Name hugo -ScriptBlock {hugo server} -WorkingDirectory $HOME/Developer/git/myhugosite

Visit the local site in your default web browser. Port 1313 is the default, but a different port is dynamically assigned if the default port is already in use.

1Start-Process http://localhost:1313/

The website should look similar to the following.

Visit the local site in your web browser

Stop the Hugo server by pressing CTRL+C in the PowerShell console or by stopping the PowerShell job, depending on how you started it.

1Get-Job | Stop-Job -PassThru | Remove-Job

Create a repository on GitHub

Add and commit the changes to your local Git repository.

1git add .
2git commit -m 'Initial commit'
1[main (root-commit) 89e1737] Initial commit
2 6 files changed, 50 insertions(+)
3 create mode 100644 .gitignore
4 create mode 100644 archetypes/default.md
5 create mode 100644 content/posts/my-first-article.md
6 create mode 100644 go.mod
7 create mode 100644 go.sum
8 create mode 100644 hugo.toml

Tip: To authenticate with GitHub using the GitHub CLI, run gh auth login.

Create a new repo on GitHub using the GitHub CLI and push your local clone to it. You can only use GitHub Pages with a public repo if you're using a free GitHub account. Private repos with GitHub Pages require a paid GitHub account.

1gh repo create myhugosite --public --push --source=. --description 'My hugo website'
 1✓ Created repository mikefrobbins/myhugosite on GitHub
 2  https://github.com/mikefrobbins/myhugosite
 3✓ Added remote https://github.com/mikefrobbins/myhugosite.git
 4Enumerating objects: 11, done.
 5Counting objects: 100% (11/11), done.
 6Delta compression using up to 12 threads
 7Compressing objects: 100% (7/7), done.
 8Writing objects: 100% (11/11), 1.31 KiB | 1.31 MiB/s, done.
 9Total 11 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
10To https://github.com/mikefrobbins/myhugosite.git
11 * [new branch]      HEAD -> main
12branch 'main' set up to track 'origin/main'.
13✓ Pushed commits to https://github.com/mikefrobbins/myhugosite.git

Deploy the site to GitHub Pages

Enable GitHub Pages and set it to deploy via GitHub Actions. Use the GitHub CLI with the GitHub API to update the repo settings.

1gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/<github-username>/myhugosite/pages -F "source:branch=main" -F "source:path=/" -F "build_type=workflow"
 1{
 2  "url": "https://api.github.com/repos/mikefrobbins/myhugosite/pages",
 3  "status": null,
 4  "cname": null,
 5  "custom_404": false,
 6  "html_url": "https://mikefrobbins.github.io/myhugosite/",
 7  "build_type": "workflow",
 8  "source": {
 9    "branch": "main",
10    "path": "/"
11  },
12  "public": true,
13  "protected_domain_state": null,
14  "pending_domain_unverified_at": null,
15  "https_enforced": true
16}

Add the GitHub Actions workflow folder locally.

1New-Item -Path $HOME/Developer/git/myhugosite/.github/workflows -ItemType Directory -Force
1Directory: /Users/mikefrobbins/Developer/git/myhugosite/.github
2
3UnixMode           User Group         LastWriteTime         Size Name
4--------           ---- -----         -------------         ---- ----
5drwxr-xr-x mikefrobbins staff      10/25/2023 20:15           64 workflows

Save the following GitHub Actions workflow to a file named Hugo.yaml in the workflow folder. Run hugo version on your computer to determine what version of Hugo you're using and update the version in the workflow. The following workflow is a modified version of the one found in the Host on GitHub Pages section of the Hugo website.

 1$workflow = @'
 2# Sample workflow for building and deploying a Hugo site to GitHub Pages
 3name: Deploy Hugo site to Pages
 4
 5on:
 6  # Runs on pushes targeting the default branch
 7  push:
 8    branches:
 9      - main
10
11  # Allows you to run this workflow manually from the Actions tab
12  workflow_dispatch:
13
14# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
15permissions:
16  contents: read
17  pages: write
18  id-token: write
19
20# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
21# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
22concurrency:
23  group: "pages"
24  cancel-in-progress: false
25
26# Default to bash
27defaults:
28  run:
29    shell: bash
30
31jobs:
32  # Build job
33  build:
34    runs-on: ubuntu-latest
35    env:
36      HUGO_VERSION: 0.119.0
37    steps:
38      - name: Install Hugo CLI
39        run: |
40          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
41          && sudo dpkg -i ${{ runner.temp }}/hugo.deb
42      - name: Install Dart Sass
43        run: sudo snap install dart-sass
44      - name: Checkout
45        uses: actions/checkout@v3
46        with:
47          submodules: recursive
48          fetch-depth: 0
49      - name: Setup Pages
50        id: pages
51        uses: actions/configure-pages@v3
52      - name: Install Node.js dependencies
53        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
54      - name: Build with Hugo
55        env:
56          # For maximum backward compatibility with Hugo modules
57          HUGO_ENVIRONMENT: production
58          HUGO_ENV: production
59        run: |
60          hugo \
61            --gc \
62            --minify \
63            --baseURL "${{ steps.pages.outputs.base_url }}/"
64      - name: Upload artifact
65        uses: actions/upload-pages-artifact@v1
66        with:
67          path: ./public
68
69  # Deployment job
70  deploy:
71    environment:
72      name: github-pages
73      url: ${{ steps.deployment.outputs.page_url }}
74    runs-on: ubuntu-latest
75    needs: build
76    steps:
77      - name: Deploy to GitHub Pages
78        id: deployment
79        uses: actions/deploy-pages@v2
80'@
81Set-Content -Path $HOME/Developer/git/myhugosite/.github/workflows/hugo.yaml -Value $workflow -Force

Add, commit, and push the changes. Pushing the changes to the main branch of your GitHub repo triggers the GitHub Actions workflow. The GitHub Action is a build-and-deploy workflow that builds and deploys the site to GitHub Pages. The GitHub Actions workflow automatically runs when you update the main branch, and you can manually trigger it by running gh workflow run 'Deploy Hugo site to Pages'.

1git add .
2git commit -m 'Added GitHub Action workflow'
3git push
 1[main 7afd717] Added GitHub Action workflow
 2 1 file changed, 78 insertions(+)
 3 create mode 100644 .github/workflows/hugo.yaml
 4
 5Enumerating objects: 6, done.
 6Counting objects: 100% (6/6), done.
 7Delta compression using up to 12 threads
 8Compressing objects: 100% (3/3), done.
 9Writing objects: 100% (5/5), 1.35 KiB | 1.35 MiB/s, done.
10Total 5 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
11remote: Resolving deltas: 100% (1/1), completed with 1 local object.
12To https://github.com/mikefrobbins/myhugosite.git
13   89e1737..7afd717  main -> main

You can see the name of all your GitHub Actions workflows using the GitHub CLI.

1gh workflow list
1NAME                       STATE   ID
2Deploy Hugo site to Pages  active  89112170

Check the status of the GitHub Actions workflow. It may take a few minutes for it to complete.

1gh workflow view 'Deploy Hugo site to Pages'
 1Deploy Hugo site to Pages - hugo.yaml
 2ID: 89112170
 3
 4Total runs 1
 5Recent runs
 6   TITLE                         WORKFLOW                   BRANCH  EVENT  ID
 7✓  Added GitHub Action workflow  Deploy Hugo site to Pages  main    push   8223964591
 8
 9To see more runs for this workflow, try: gh run list --workflow hugo.yaml
10To see the YAML for this workflow, try: gh workflow view hugo.yaml --yaml

Tip: The workflow failed if you didn't remove the Go patch version from the go.mod file, as described earlier in this article.

Once the GitHub Actions workflow completes successfully, visit the public website in your default web browser.

1Start-Process https://<github-username>.github.io/myhugosite/

The website should look identical to the local one you previewed and similar to the following.

Visit the public website

Clean up resources

If the resources created in this article aren't needed, you can delete them by running the following commands.

Warning: The following commands perform non-recoverable destructive changes. If resources outside the scope of this article exist in the specified local directory or GitHub repo, these commands also delete them.

The following commands delete the site, GitHub repo, and local directory used in this article. I've placed a comment symbol before each command so you don't run them accidentally.

Tip: Deletion requires authorization with the delete_repo scope. To authorize, run gh auth refresh -s delete_repo.

1# Get-Job -Name hugo -ErrorAction SilentlyContinue | Stop-Job -PassThru | Remove-Job
2# Remove-Item -Path $HOME/Developer/git/myhugosite -Recurse -Force
3# gh repo delete myhugosite --yes
1✓ Deleted repository mikefrobbins/myhugosite

Summary

This article outlined how to set up a free blog using Hugo and GitHub Pages. It covered essential prerequisites, installation, and content creation. It also provided step-by-step instructions for local testing and online deployment through GitHub.

While outside the scope of this article, I recommend using a custom domain name for your blog in case you decide to move it somewhere else; you can maintain the existing URLs for your content. It's also easier to remember and share with others.

References