Building this site with Hugo and GitHub Pages

2025-07-23 Β· 17 min read

At the bottom of each page of this website, it says:

Published with Hugo Blox Builder β€” the free, open source website builder that empowers creators.

Similar notes can be found on many websites. For someone like me who might be interested in how a particular site was created, it offers a place to start looking. However, I found it easy to initially become lost in the vast array of resources available for most popular static website generators. Here, I detail exactly how I used Hugo and GitHub Pages, among other tools, to set up the website you’re reading.

Here’s an overview:

Introduction

Many academics will tell you that having an up-to-date personal website can help you disseminate research and make connections for future projects and career opportunities. I had a personal website hosted through SquareSpace for years. I found it fairly easy to set up initially, and it was simple to make updates. While it was difficult to figure out how to add equations in text and the templates were a bit limiting, it automatically resized the site for all types of devices and I never needed to worry about any backend coding for it. However, at CA$252 a year (circa 2025) for a “Basic” site, I decided I was willing to forego the ease and simplicity of SquareSpace for a free and open source option, Hugo.

I was inspired by a post on Nicole Paul’s website, How did I make my personal academic website?. I share Nicole’s desire to be able to maintain my personal website using a text-based editor. In addition, using Hugo allows me to leverage tools that I am already familiar with to maintain this site, things I use every day like VSCodium, Git, and GitHub.

A big draw for me to use Hugo is that it allows me to launch a local server where I can view my website while it updates live with each change I save in my local repository. This way, I can work on changes and make commits all locally without changing the publicly-viewable version of the site. Then, when I’m satisfied with the changes, I can simply push those commits to the remote repository and the magic of GitHub Actions automatically propagates those changes to the public site.

GitHub provides free hosting through GitHub Pages, however I do pay CA$30.44 a year to Hover for my custom domain name. The whole process of setting this site up had a bit of a learning curve, but I found it satisfying to learn how to maintain my own site at a deeper level than when I was using SquareSpace.

Tools Used

  • Hugo
    • An open source web app to generate websites.
  • Hugo Blox
  • GitHub Pages
    • A platform built in to GitHub through which to host websites.
  • GitHub Actions
    • An automation system built in to GitHub, allowing you to have workflows run automatically when certain triggers occur, like a push to the repository.
  • Hover
    • A custom domain name provider.

Installing Hugo

Hugo has a page with straight-forward installation instructions. Since I was using a MacBook and already had the Homebrew package manager installed, I first just ran their suggested command: brew install hugo which fairly quickly installed the latest stable version of Hugo (v0.147.9 as of 2025-07-07), which I could double check by running hugo version. However, I immediately ran into a versioning issue. The resolution was to downgrade Hugo to v0.145.0, which I explain how to do below. But, if I was installing Hugo for the first time and planned to use the same academic-cv template as this website does, I’d install Hugo v0.145.0 directly. Your mileage may vary depending the version of Homebrew, but the following command should do the trick:

brew install hugo@0.145.0

If you aren’t interested in using the academic-cv template and just want the latest version of Hugo, you can checkout the Hugo QuickStart Guide.

Setting up the theme

The theme I chose for my site, academic-cv, is the same as used by Nicole Paul. I decided to follow the Hugo Blox (the makers of the template) documentation page Build your online resume, going to the GitHub page for the academic-cv template and clicking the “Use this template” button to create my own repository for this website. Note that, the name selected for the repository in this step will be part of the published website’s URL unless you choose to purchase a custom domain name for it.

After creating a repository on GitHub, I went to my terminal and did a git clone in the directory in which I wanted to keep the code on my local machine which, here, I’ll call <repository_path>. Once the repository was cloned, I ran the following command to make sure everything was working:

hugo server

However, I ran into a couple different errors.

Installation issues

(click to expand)

Upon first running hugo server, I encountered the error:

Error: failed to init modules: binary with name "go" not found

The fix for this was pretty simple, I just needed to install Go. As mentioned in the issue Can’t find Go Binary to init modules on Hugo’s site, I needed to restart my terminal after the installation for Go to be recognized.

The next issue took a bit more work to figure out. I was now encountering the error:

Error: command error: failed to load modules: module "github.com/HugoBlox/hugo-blox-builder/modules/blox-tailwind" not found

After a while of digging through forum posts and GitHub issues, I found the page Hugo Blox is incompatible with Hugo 0.147 where GitHub user KeepNoob shared their experience of not being able to use Hugo Blox with Hugo v0.147, but successfully being able to run with v0.145. I needed to follow these instructions for downgrading Hugo with brew using the 0.145.0 bottle. After that was complete, I was successfully able to run hugo server.

If you happen to be following this post to set up your own site with the academic-cv, you should be able to directly install v0.145 as mentioned above.

Hugo local server

Something I appreciate about Hugo is how easy it is to launch a live-updating local instance of my site by navigating my terminal to my site’s repository and running:

hugo server

This outputs something like:

Watching for changes in /Users/<username>/{Documents,Library}
Watching for config changes in /Users/<username>/<repository_path>/config/_default, /Users/<username>/Library/Caches/hugo_cache/modules/filecache/modules/pkg/mod/github.com/!hugo!blox/hugo-blox-builder/modules/blox-plugin-netlify@v1.1.2-0.20231209203044-d31adfedd40b/config.yaml, /Users/<username>/Library/Caches/hugo_cache/modules/filecache/modules/pkg/mod/github.com/!hugo!blox/hugo-blox-builder/modules/blox-tailwind@v0.3.1/hugo.yaml, /Users/<username>/<repository_path>/go.mod
Start building sites … 
hugo v0.145.0+extended+withdeploy darwin/amd64 BuildDate=2025-02-26T15:41:25Z VendorInfo=brew


                   | EN  
-------------------+-----
  Pages            | 83  
  Paginator pages  |  0  
  Non-page files   | 43  
  Static files     |  2  
  Processed images | 80  
  Aliases          | 22  
  Cleaned          |  0  

Built in 1381 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) 
Press Ctrl+C to stop

Here, I’ve replaced the username on my local machine with <username> and the path to my website’s repository with <repository_path>. Upon opening the displayed URL (in this case, http://localhost:1313/) in a browser, I can see what my website looks like. At this point, my terminal is be actively running a local server for my site so if I close it or press Ctrl+C, as mentioned in the output, the server will stop. I noted that, if the server stops, the page in my browser will still display until I try to navigate to a different page.

While this local server is running, any time I save changes to a file in the repository, the server will auto-update to show the results of the changes. I find this functionality incredibly useful, allowing me to easily test tweaks to my site as I go.

Editing the site

The subsections below detail how I made a lot of the changes on this site. If you are interested in how to get the site live, you can skip to Publishing the site.

Site layout

The academic-cv template comes with a number of different sections pre-loaded such as resume, talks, papers, projects, etc. I wasn’t interested in taking the time to fill in every single section before I first made my site live, so I disabled parts of the navigation menu. In the file config/_default/menus.yaml, I can simply comment out the name/url/weight of a menu item to make it not appear in the navigation bar.

# in config/_default/menus.yaml
...
main:
  - name: Home
    url: /
    weight: 10
  # - name: Papers
  #   url: /#papers
  #  weight: 11
  # - name: Talks
  #   url: /#talks
  #   weight: 12
  # - name: News
  #   url: /#news
  #   weight: 13
  - name: Resume
    url: resume/
    weight: 20
...

However, doing this will not disable the page. If I use the exact url, I can still access those pages on the live site, but there won’t be a button making it discoverable to others.

Changing the date format

At the top and bottom of this page, the date on which this guide was posted is listed. The format of that date, and all other dates on the site, is controlled centrally. This means, if I want to change the date format, I don’t need to change each page individually. In the config/_default/params.yaml file, there is a block which looks like:

# in config/_default/params.yaml
...
# Localization
locale:
  date_format: '2006-01-02'
  time_format: '15:04'
...

The Hugo Blox Docs for Localization have a table of acceptable formats for each field (year, month, day, hour, etc.). I can change the date format to whatever I would like, however it is important that the actual date used in the .yaml file is January second, 2006 at 3:04:05 in the afternoon in Mountain Standard Time. From this forum post on Hugo’s site, I found out that the reason for this comes from the way that the Go language handles datetime objects. The relevant section of the Go language documentation reads:

The reference time used in these layouts is the specific time stamp:

01/02 03:04:05PM '06 -0700

(January 2, 15:04:05, 2006, in time zone seven hours west of GMT). That value is recorded as the constant named Layout, listed below. As a Unix time, this is 1136239445. Since MST is GMT-0700, the reference would be printed by the Unix date command as:

Mon Jan 2 15:04:05 MST 2006

It is a regrettable historic error that the date uses the American convention of putting the numerical month before the day.

The resume page

The main feature of the academic-cv template is the resume. All of the information that appears on the Resume page is stored in the content/authors/admin/_index.md file. Some of the information in that file, such as the “Interests” and “About Me” sections, is loaded on the Home page. One of the first things I did before setting my site live was to change all the information in that _index.md file to be personal to me, as well as replace the content/authors/admin/avatar.jpg image with my own headshot. For sections of the resume that I wasn’t interested in including right away, I simply commented them out. This allows me to leave the structure of those sections in case I decide to include them later.

Adding icons

Under the skills section of the content/authors/admin/_index.md file, there are references to icons which show up next to each listed skill. If you do not want to display icons next to your listed skills, you can delete or comment out the lines that start with icon:.

# in content/authors/admin/_index.md
...
skills:
  - name: Technical
  # See this site for icons: https://devicon.dev/
    items:
      - name: Python
        description: ''
        percent: 80
      #  icon: devicon/python
...
      - name: Git
        description: ''
        percent: 40
        icon: devicon/git
      # - name: GitHub
      #   description: ''
      #   percent: 40
      #   icon: devicon/github
...

The Hugo Blox Docs Icons section explains where the site looks for icons depending on what reference you give under each entry. I found the icons I use on my resume page in the following packs:

Setting the favicon

A favicon is the little symbol for a website that you will see in various places in your browser. Most commonly, it appears in the tab next to the name of the webpage. The Hugo Blox Docs Website icon section explains that the assets/media/icon.png file is what the site will use as a favicon. For example, this is the favicon for my site:

Favicon for mikhailschee.com

I used the Favicon Generator site to “Generate From Text” with the following parameters:

  • Text: MS
  • Font Color: #53D399
  • Background Color: #27272A
  • Background: Square
  • Font Family: Roboto Mono
  • Font Variant: Regular 400 Normal
  • Font Size: 100

Then, I simply replaced the file in assets/media/icon.png using the same filename. The next time I pushed changes to my site, the favicon updated to the new image.

Publishing the site

Being able to view the live-updating site locally is very convenient for testing changes. However, the point of my personal website is to have it publicly accessible. This is where GitHub Pages comes in.

Hosting on GitHub Pages

To connect what I’d built with locally with a publicly accessible host, I followed the guide on how to Host Hugo on GitHub Pages. Funnily enough, that guide was written by the same guy, jmooring (see the screenshot in Step 11), who wrote the instructions for downgrading Hugo with brew I used when having installation issues.

When that guide talks about the “site configuration,” for the academic-cv template, is in config/_default/hugo.yaml, which has the same file name but is different from the file mentioned later for the workflow. I changed the site configuration as shown here:

# in config/_default/hugo.yaml
...
caches:
  images:
    dir: :cacheDir/images
...

For the .github/workflows/hugo.yaml file, the guide linked above has been updated since I deployed my site. The version of that .yaml file I used is shown below.

`.github/workflows/hugo.yaml` (click to expand)
# Sample workflow for building and deploying a Hugo site to GitHub Pages
name: Deploy Hugo site to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches:
      - main

  # 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:
      DART_SASS_VERSION: 1.89.2
      HUGO_VERSION: 0.145.0
      HUGO_ENVIRONMENT: production
      TZ: America/Los_Angeles
    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: |
          wget -O ${{ runner.temp }}/dart-sass.tar.gz https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz \
          && tar -xf ${{ runner.temp }}/dart-sass.tar.gz --directory ${{ runner.temp }} \
          && mv ${{ runner.temp }}/dart-sass/ /usr/local/bin \
          && echo "/usr/local/bin/dart-sass" >> $GITHUB_PATH
      - 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: Cache Restore
        id: cache-restore
        uses: actions/cache/restore@v4
        with:
          path: |
            ${{ runner.temp }}/hugo_cache
          key: hugo-${{ github.run_id }}
          restore-keys:
            hugo-
      - name: Configure Git
        run: git config core.quotepath false
      - name: Build with Hugo
        run: |
          hugo \
            --gc \
            --minify \
            --baseURL "${{ steps.pages.outputs.base_url }}/" \
            --cacheDir "${{ runner.temp }}/hugo_cache"
      - name: Cache Save
        id: cache-save
        uses: actions/cache/save@v4
        with:
          path: |
            ${{ runner.temp }}/hugo_cache
          key: ${{ steps.cache-restore.outputs.cache-primary-key }}
      - 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

By taking the time to set up the GitHub Actions described in that guide, my public site updates with my changes every time I push any commits to the remote repository.

I can check the status of my running actions by going to the GitHub page for my repository and clicking into the “Actions” tab. If those actions start running correctly, a publicly-available version of the site should be available to visit at https://<GitHub_username>.github.io/<repository_name> in about 10 minutes. By going to the “Settings” tab of my GitHub repository (not the “Settings” tab of my GitHub account), and navigating to “Pages” in the sidebar, I can see a notification that my site is live with a button to “Visit Site.” If you aren’t interested in connecting your site to a custom domain, then your setup is done. All you need to worry about now is continuing to edit the site to keep the content up-to-date.

Connecting a custom domain name with Hover

I have my personal domain name through Hover. I don’t necessarily recommend it over any other provider, it just so happens to be where I purchased mikhailschee.com many years ago. Previously, I had my SquareSpace site linked to my Hover domain, so I first needed to unlink that connection.

On Hover’s site, I found the “Control Panel” for my domain then clicked on the “DNS” tab. There, it shows all the records of various types for my domain. As mentioned in the stackoverflow post Github Pages: Custom domain setup with Hover, I needed to delete all the “A” type records in order to unlink the domain from SquareSpace.

Before adding the records to link my domain to my GitHub pages site, I followed the recommendation in the article Verifying your custom domain for GitHub Pages to increase the security of using a custom domain and avoid takeover attacks. First, I navigated to the “Pages” settings of my GitHub account as a whole by clicking the gear under my GitHub profile picture (note that this is different than the “Pages” settings of the website repository, a page which I’ll use later to connect my Hugo site to my custom domain). From here, I added my custom domain of mikhailschee.com. This gave me information to be added to a DNS TXT record in Hover:

  • Hostname: _github-pages-challenge-scheemik
  • Content: <DNS_code_value>
    • The <DNS_code_value> is a long string of letters and numbers.

Back on the DNS tab of the Hover control panel, I clicked “Add a record”, selected the type “TXT”, and entered the above values. After about half an hour, the GitHub “Pages” settings listed my custom domain under “Verified domains.”

Next, I followed the instructions in Managing a custom domain for your GitHub Pages site. I navigated to my site’s repository on GitHub and clicked on the “Settings” tab. Sometimes this tab isn’t visible if the browser window is too narrow, this is not the “Settings” under the GitHub profile picture. On the “Pages” tab under “Custom domain,” I entered mikhailschee.com. Hitting “Save” automatically generates a file called CNAME in the base repository directory where the contents are simply the custom domain.

Once again back on the DNS tab of the Hover control panel, I added four type “A” records, all of them with the hostname @ and the following IP addresses for GitHub Pages:

  • 185.199.108.153
  • 185.199.109.153
  • 185.199.110.153
  • 185.199.111.153

To confirm those records got set correctly, I ran this command in my terminal:

$ dig mikhailschee.com +noall +answer -t A

; <<>> DiG 9.10.6 <<>> mikhailschee.com +noall +answer -t A
;; global options: +cmd
mikhailschee.com.       900     IN      A       185.199.109.153
mikhailschee.com.       900     IN      A       185.199.108.153
mikhailschee.com.       900     IN      A       185.199.111.153
mikhailschee.com.       900     IN      A       185.199.110.153

As recommended in Nicky Marino’s blog post Pointing a Github Pages Repo to a Hover Domain and Brendan Quinn’s blog post Pointing Hover Domain to Github Pages, as well as being under Configuring an apex domain and the www subdomain variant section of the GitHub documentation, I added a type “CNAME” record as well:

  • Hostname: www
  • Target Name: scheemik.github.io

After a few hours of waiting for the changes to propagate, the site I’d created was now accessible on my custom domain.

Conclusions

While the process of setting up this site with Hugo wasn’t exactly straightforward, I appreciated how much I learned along the way. There are plenty of things I’m hoping to change about how my website works, but for now, I’m happy to have a site which I can preview locally and reliably update with a simple git push.