Hugo and GitHub Pages Tutorial


I used the static site generator Hugo and GitHub Pages hosting to build this website. Aside from one or two mistakes on my end, it was relatively easy to learn, customize, and deploy. That said, I did have to do some guessing and hopping between not-always-forthright tutorials.1 In the interests of learning more and helping others avoid my mistakes, I’ll walk through the whole process here from setup to deployment.2

There are many approaches to building websites; why this one? Content Management Systems (CMS) like Wordpress, Drupal, or Wix have user interfaces for managing content stored in a database and assembled dynamically on access. They require no coding, and dynamic sites are also better able to handle complex features or large numbers of pages. Static Site Generators (SSG) like Hugo or Jekyll have several rival benefits, though. Because site content lives locally or in a Git repository, it is more accessible, portable, and transparent. SSGs facilitate easier and much more precise theme customization, and changes can be tested instantly before going live. Mobirise is a code-free drag-and-drop website builder; it combines some pluses of SSGs with the code-free aspect of CMSs, but it limits the ability to edit site code. Hugo and Jekyll have more of a learning curve, but they allow you to get under the hood and get experience with HTML and CSS. After experimenting with both I chose Hugo over Jekyll because installation is more streamlined and file storage more intuitively centralized.

There are all kinds of hosting options as well; being able to choose is another benefit of SSGs compared to CMSs. I know many people who using Reclaim Hosting and like it. I use GitHub Pages because it is free, because I already use GitHub for various purposes, and because it provides an ideal opportunity to get more comfortable with Git. Better yet, GitHub Pages can also host Project websites for free under the same directory with a similar workflow.


Get Git and GitHub

First sign up for a GitHub account. Note when picking a user name that the website will at least initially have a url that includes it. GitHub will ask for email verification as well.

On Windows, install Chocolatey via terminal for managing software packages. All the lines of code in this tutorial should be entered into a command line terminal, and the first few need to be run as administrator. To locate the terminal, search “cmd” and right click to select “run as administrator.” It’s important to respect case sensitivity: if prompted to confirm downloads, enter Y for yes or A for all.

Copy and paste the following (all one line):

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command " [System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"

Verify with:

choco version

On Mac, install Homebrew for managing software packages and change the remaining choco commands in this tutorial to brew.

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

brew --version

From here install Git. You may be prompted to run additional scripts as well.

choco install git

Verify with:

git version

Configure user information to match your GitHub name/email:3

git config --global user.name "exampleusername"

git config --global user.email "username@example.com"

One last preliminary. Markdown files are used for managing content in Hugo; HTML (HyperText Markup Language) and (mostly) CSS (Cascading Style Sheets) files are used for generating the site structure and appearance, respectively. Basic Notepad software can open all these file types, but it can interfere with character encoding. Plus, markup languages like HTML and CSS can be hard to read on their own. I highly recommend downloading a text editor built for these purposes; I use Atom.


Using Hugo

Next, to install Hugo.

choco install hugo -confirm

And verify:

hugo version

Navigate to where a directory for all Hugo files will live; presumably somewhere like:

cd C:/User/Documents

Make the new directory for all Hugo files itself and change directories into it:

mkdir Hugofiles

cd Hugofiles

Create a directory and the necessary files/subdirectories for a new site (note that this command begins with hugo, because it is Hugo-specific). This will be something of a sandbox for trying things out: when ready to deploy a website we’ll create a second directory in /Hugofiles/ just for it so that this first sandbox directory can continue to be used for experimenting without risk.

hugo new site sandbox

Change directories to this new sandbox directory and create what will be a post (as well as a posts directory, via the /) and a page:

cd sandbox

hugo new posts/first-post.md

hugo new about.md

Now is the time to select a theme if you haven’t yet. Theme documentation is often helpful, but some require more editing or subdirectory structuring than others. Themes with basic structures and minimal bells/whistles are the friendliest to use. You will also want to check that the demo page functions: missing images is ok, but if the layout is jumbled stay away. The added benefit of simpler themes is that they are also easier to switch between if desired.

That said, there are many more great, easy-to-use options available, especially if you browse with the above tips in mind. After deciding, add a theme to the /themes/ subdirectory in the terminal from the site project directory.

git clone https://github.com/Track3/hermit.git themes/hermit

Copy the file named config from the /exampleSite/ directory in /themes/ and paste it in the root site project directory, replacing the config file that was there. This file governs site basics and cross-site variables; as these vary from theme to theme, it’s best to use the example as a template. Remove all text in the baseURL field so that it is only "". Edit site name, author name, social media links, theme color, and [[menu.main]] fields, making sure the fields match file names and directory paths exactly. Depending on the theme, some of these fields can be single pages and others directories of multiple pages in blog post format. Some themes allow multiple post-style sections; others use tags; some allow multiple language settings. Every theme has additional unique configuration options.

All content lives in the content directory. Content intended for blog post format should be created in a subdirectory, e.g. as above in posts, unlike content intended for main menu page format. Open one of the new post files just made (in /content/). New posts are listed as drafts in their header by default until changed; only when the value of draft is false will the file be rendered as part of the website. When writing, use basic markdown syntax for basic stylization such as emphasis (*like so*), headers (## Like So), links ([link name](https://linktext.com)), and images (![](/image_name.jpg)). Hugo will transform this into the appropriate HTML.

To attach a file, whether for embedding or downloading, first paste the file into the static directory. In the markdown file for the page it will be attached to, write the name of the file as if it were a link, prefixed with a single backslash (/).

When ready, generate a live-updating local test of the site:

hugo server

View in browser at http://localhost:1313/. End the test with ctrl+c in the terminal.


Tips for Building & Troubleshooting

If posts or pages aren’t showing up in the local preview, make sure that the file names and paths match exactly what is listed in the config file (ex., /posts/ vs /post/ or /en/posts/ vs just /posts/). If the config file doesn’t include menu parameters, check the theme’s /exampleSite/ directory for additional files that specify menu items (ex., something like _index.md in /content/ or even in /data/). Read them, copy them to the equivalent location in the site project directory itself, and edit there as necessary. The same goes for when a theme uses images in post previews or banners: check the example site to make sure that the image is stored in the correct location and being invoked with the correct syntax.

More complex site structures can be more challenging. Single-page multi-section themes will often have either a large index file that content will need to be inserted into or index files for each section in /content/; this will take a bit more time to become familiar. Portfolio-style themes can require extra finicking with subdirectory structures, page-level syntax, and image dimensions. Saving images as .svg files may be necessary, as this format is most amenable to scaling.

Some Hugo themes use SASS/SCSS instead of CSS. In order to generate these pages it is necessary to install the “extended” version of Hugo (otherwise the terminal will give you an error even when launching a local test). Thankfully it is very easy to do this from the command line:

choco install hugo-extended -confirm

If given a “module not found” error when generating the local preview, check the config file for a line reading themesDir = "../.." and delete it. If given an unknown characters or encoding error, it is probably from editing and saving theme files in programs that try to overwrite their character encoding, like plain Notebook or Microsoft Word (again, I recommend Atom).

Themes directories are all organized similarly, which makes it easy to customize HTML or CSS. When doing so, first copy and paste the pertinent files from the /themes/ directory to the root site project directory, recreating relative paths (i.e., /website/themes/hermit/assets/css/normalize.css would be copied to /website/assets/css/normalize.css). Hugo prioritizes any root files over those found in /themes/ when generating sites, and moving files to root facilitates version control. For that reason, make sure to edit these and not the originals. (If changing themes later, however, it will be necessary to empty these directories.)

Customizing HTML or CSS can vary considerably from theme to theme. Styling tasks, like changing fonts, sizes, or colors – a matter for CSS and thus usually in an /assets/ file with a name like template or predefined – are fairly straightforward. This is a good place to begin. In the Hermit theme one could open /assets/_predefined.scss and change $fonts: to, say “Georgia” or another universal (or imported) font.

Removing features (like icons or “reading time” estimates) is generally easier than adding new ones; just excise the corresponding HTML. HTML files are in /layouts/ and divided up into parts. Rather than deleting a feature – since you may wish to restore it in the future – I suggest using enclosing it in comment tags so that it remains in the file but won’t actually render. <!-- These are HTML comment tags -->, and /* These are CSS comment tags */. In the Hermit theme one could remove word counts by opening /layouts/posts/single.html and placing the whole <p></p> tag containing “wordCount” in comment tags.

The Inspect tool in the Chrome browser (an option when you right-click any blank space on a webpage) is a nice shortcut to identifying the code that produces a particular feature. Expand elements in the upper right as necessary by clicking the arrow; elements on the page will highlight when hovering over the HTML tag in which they are nested. Once a tag is identified, run a search in the terminal to identify where the corresponding code resides. In Windows, use findstr (/n gives the line number for each hit, /s searches subdirectories as well, *.* searches all files of all extensions):

findstr /n /s "text you're searching for" *.*

On Mac and BASH terminals, use grep (- adds options, r searches subdirectories as well, n gives the line number for each hit, o only shows the matching text):

grep -rno "text you're searching for"

Hosting on GitHub

Create a repository on GitHub for the Hugo directory for this project, for example, mywebsite. This isn’t where the site itself will live: it’s where all the files used to generate the site will live (i.e., everything currently in C:/User/Documents/Hugofiles/sandbox). Initialize with a readme and an MIT License. The site project repository can be set to private (for example, if you wanted to keep anyone from snooping into your drafts).

Then create a second GitHub repository for the generated website files. The name of this repository needs to follow the format <USERNAME>.github.io. Mine, with the user account “azl-test,” is azl-test.github.io (https://azl-test.github.io/). This repository, the website repository itself, will need to remain public.

Copy the url to the site project repository. Return to the terminal and change directories to wherever you want this project to live. Now clone the repository; log-in prompts may appear if set to private.

cd C:/User/Documents/Hugofiles

git clone https://github.com/<USERNAME>/mywebsite.git

cd mywebsite

In a file viewer window, copy and paste the contents of the site project from the sandbox directory into this new directory. Delete any extra themes after doing so. Verify it still works with hugo server and, when done, end the test with ctrl+c. This mywebsite directory and its corresponding repository will now be just for this particular website: the sandbox directory will remain for any unrelated tinkering or preparations for additional website projects.

Now it’s time to generate the website and push it to the .github.io repository. First clear the public directory in case you made it by accident. On Windows, run:

rd public /s

On Mac, run:

rm -rf public

I can’t stress this enough: do not repeat this step and do not delete the public directory from this point on. Doing so will confuse git and prevent you from being able to push changes.

Hugo generates the actual website files in your site project directory a subdirectory called /public/. The pairing of Hugo and GitHub requires us to push the generated website files themselves in a submodule repository. Git submodules are basically repositories nested in other repositories. So by making the .github.io repository a submodule of the mywebsite repository, we’re replicating the structure Hugo uses by making the rendered website files in a /public/ subdirectory inside the site project directory mywebsite itself. (Note that the following is one line, not two.)

git submodule add -b master https://github.com/<USERNAME>/<USERNAME>.github.io.git public

That covers all the setup. You won’t need to and, again, should not, repeat them.

Now to generate the site and push changes to Git. Hugo recommends you create a script to do these steps all in one. Fair enough. They recommend saving this as deploy.sh (in a text editor like Atom, simply create a new file, paste this in, “save as,” and make sure to type the .sh extension). Save it in the project site directory (i.e., not in /public/).

#!/bin/sh

# If a command fails then the deploy stops
set -e

printf "\033[0;32mDeploying updates to GitHub...\033[0m\n"

# Build the project.
hugo # if using a theme, replace with `hugo -t <YOURTHEME>`

# Go To Public folder
cd public

# Add changes to git.
git add .

# Commit changes.
msg="rebuilding site $(date)"
if [ -n "$*" ]; then
	msg="$*"
fi
git commit -m "$msg"

# Push source and build repos.
git push origin master

(You could just as well do this by running the hugo, cd public, git add ., git commit -m "write a commit message here to describe the changes", and git push origin master commands in order, but the script streamlines deployment.)

On Windows, in your project site directory run:

deploy.sh "write a commit message here to describe the changes you made"

On Mac, in your project site directory run:

./deploy.sh "write a commit message here to describe the changes you made"

Prompts may ask for Git username and password.

Changes should be live shortly at https://<USERNAME>.github.io (a hard refresh might be necessary). Future edits will only require this single line invoking the deploy script.

Push changes to the site project repository as well by using the git add ., git commit -m "a commit message", and git push origin master commands in order. It is important to keep this repository up to date with local changes because these are the files from which the website repository is generated (they also contain any draft posts/pages). Should something happen to the files on your local computer, you will be able to git pull or git clone them again to recover them.

What if the local test works but the website looks goofy? Make sure the “baseURL” field in the config file is either empty or matches the .github.io address exactly.

What if the local test works but the website doesn’t show at all? First check the GitHub webpages for the site project repository and the .github.io repository to see what, if any, commits went through. The most common issues involve the /public/ directory-submodule setup, usually as a result of not clearing /public/ before setting up the submodule or deleting it afterwards. This is probably the case if the terminal returns the error message “fatal: in unpopulated submodule” or if commits present in the site project repository are absent from the .github.io repository.

Since there are a couple different ways to get into this knot, here’s the most encompassing/painless way to untie it. Four steps:

  1. Delete /public/ from your site project directory and then push the changes from that directory in the terminal with the git add ., git commit -m "deleted public directory", and git push origin master commands in order.
  2. Delete the .github.io repository by going to the corresponding GitHub webpage, clicking the “Settings” tab, scrolling to the Danger Zone at the bottom, and selecting “Delete this repository.” GitHub will ask you to type the full name of the repository to make sure you’re sure.
  3. Recreate the .github.io repository from the ashes the same way as before, at the start of this section of the tutorial.
  4. Repeat the git submodule line above and run the deploy.sh script again with an appropriate commit message.

Custom Domain Name Setup

I found GitHub’s documentation confusing; here is an attempt at a layman’s explanation of what worked for me.

Registering a domain name itself is a fairly straightforward process. I used https://www.dotster.com/.

Once the domain name is registered, a DNS (Domain Name System) record needs to point from the GitHub Pages repository to the custom domain. In a browser, navigate to the GitHub page for the website repository; click “Settings”; scroll to “Custom domain” and enter the custom domain name without https://, i.e., <YOURNAME>.com; click save. This creates a CNAME file in the repository. (After a day or so, return and select the “Enforce HTTPS” box.)

Before making any other changes, update the local repository for the webpage to include the CNAME file: return to the command line and execute the following:

cd public

git pull origin master

Another DNS record needs to point from the custom apex domain to the GitHub Pages domain. In the DNS provider control panel, create another CNAME file. Terminology can vary from DNS provider: Dotster uses “name” for the custom domain itself and “content” for where it’s pointing to. TTL can be an hour. https://<USERNAME>.github.io should now redirect to https://<USERNAME>.com.

Optionally, set up a www subdomain in the DNS provider control panel in addition to the apex domain. Create four new DNS records, this time A or ALIAS records, each pointing from the www subdomain to one of GitHub Pages' four IP addresses:

185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153

https://www.<USERNAME>.com should now also redirect to https://<USERNAME>.com.


Google Analytics and Search Console

Set up Google Analytics to track site traffic. Enter a user name, a name for the property (website), and the actual url.

Click “Get tracking ID”; after accepting the terms, it will look something like “UA-12345-1.” To find it again later, select the account, property, “Tracking Info” menu, and “Tracking Code” page.

Many Hugo themes come with Google Analytics pre-enabled. This will be included in the config file. If so, simply add your tracking ID.

If not, there are two steps. First add the tracking ID to the config file at the top of the page right after title and theme options. For themes using the .toml config format, this will look like so:

googleAnalytics = "UA-12345-1"

For themes using the .yaml config format, it will instead look like so:4

googleAnalytics: UA-12345-1

Then navigate to /layouts/partials/ and open header.html. Add this code within a <head> tag to enable Google Analytics in Hugo:

{{ template "_internal/google_analytics.html" . }}

If header.html doesn’t have a <head> tag, the theme may have its partials highly subdivided; try something like meta.html instead.

Analytics should go live shortly after local edits are pushed to the GitHub Pages repository.

Set up the Google Search Console to monitor and improve search performance. Enter the base url as a “URL prefix” and click continue. If Google Analytics is already set up on the site, the Search Console can be immediately verified by that means.

In the console itself, click “URL inspection” and select the base url. On the next page click “REQUEST INDEXING.” It can take a day or two for this to impact search results appearance.


  1. I also had the help of a developer friend when I got turned around: thanks, Josh! ↩︎

  2. This tutorial was written in part with support from the Rutgers Libraries' Graduate Specialist program and formed the basis of a workshop I taught in that capacity in Spring 2020. ↩︎

  3. Technically, only the user.email needs to match. It’s handiest, though, to keep user.name the same as well. ↩︎

  4. Hugo does also allow .json config files, but these seem to be much less common. The appropriate syntax here would be {"googleAnalytics": "UA-12345-1"}. ↩︎