Building this site with Hugo and GitHub Pages

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
- A group of accessible themes for Hugo sites based on a common set of “blocks” built with either Bootstrap or Tailwind CSS.
- 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 2006It 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:

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.
- The
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.