How to build personal blog using GitHub Pages and Hugo

You want to build a personal blog but don’t want to spend too much time building everything from scratch? Me too! After some research I got to know there are several static site generators out there and the most used generators are: Jekyll, Hexo and Hugo. All of them work perfectly with GitHub Pages. I chose Hugo as my site generator, and it worked perfectly.

What is Hugo

Hugo is a static site generator written in Go.

Why Hugo

It’s blazing fast! Hugo’s official website states it is “the world’s fastest framework for building websites” and after using it I’ve nothing to complain about the speed.

Besides, building a blog with Hugo is simple and easy. Here is how:

Step by step

Set up GitHub Pages

Head over to GitHub, login and create a new public repository named Be sure the username is exactly the same as your GitHub username. For example: my username is HuiGong-dev so my repository should be named as Check the official guide here if you meet any problem.

Install Hugo

I’m using macOS and the installation is quite simple with Homebrew:

brew install hugo

To check your installation:

hugo version

The Hugo version should show up if the installation is successful.

Create your site

hugo new site your-site-name

Replace your-site-name with the name of your site. You can name it anything you want. The command above will create a folder named your-site-name in your current working directory. For example, if the current working directory is /Users/huigong/projects, your site will be located in /Users/huigong/your-site-name. If you are not sure about your working directory, just type pwd in your terminal:

$ pwd

And the output shows your working directory.

Choose a theme

cd your-site-name
git init
git submodule add

There are around 300 themes for Hugo. I chose hyde for my blog. You can find a theme that fits you best here. If none of them is the perfect theme for you, you may consider create your own theme in the future and contribute to the Hugo community :)

Don’t forget to configure the theme in config.toml. Simply add a line theme = "hyde" in the file. Replace hyde to the name of your own theme if you picked another theme.

Build your site and push to GitHub

Creating content in Hugo is simple:

hugo new posts/

This will create a new Markdown file named Hello world in the content/posts directory. Open the file with your favorite editor (shout out to VS Code) and change the draft: true to draft: false.

Now, start the Hugo server to preview your site:

hugo server -D

You can then check your new site at http://localhost:1313/.

Next step is to configure the config.toml file. Open it with your favorite editor and set baseURL to Remember to replace username to your own username on GitHub. The code below shows how it looks like by my side.

theme = "hyde"
baseURL = ''
languageCode = 'en-us'

Great! You’re all set! Finally, it’s time to build your site:

hugo -D

Code of your site will be generated under ./public/. Change directory to public/, do git init and set remote to your GitHub pages repository. Push to your GitHub pages repository and your site should be live within seconds.

Create a blog source repository in GitHub

Okay, now your site is live, what’s next? A problem used to bother me a lot was: how can I update my blog on multiple computers? Say, I have written something for my blog on my PC at home, and now I’m on the way with a laptop. How can I continue my work? Well the answer is: create a GitHub repository just for your source code. I said “just” because the public/ directory is within the source code directory, and we need to ignore public/ and push the rest of them to GitHub. Another benefit is that you can use GitHub Actions to glue your source code and blog together, which means you don’t need to build and deploy your site every time you change anything in your site, GitHub Actions will do all the boring stuff for you. Sounds nice? Here is how:

Head over to GitHub and create a new repository with a name like blog_source or anything that reminds you it’s the source of your blog. Set it as a remote repository for your source code and add public/ to .gitignore.

touch .gitignore
echo "public/" >> .gitignore

Push it to GitHub and next time you want to edit your blog on another machine you just need to pull it from GitHub and continue the work.

Automate the deployment using GitHub Actions

This is one of the exciting parts of building a blog with GitHub Pages. Before building this blog I knew almost nothing about GitHub Actions but after using it, it was amazing! Automating the boring stuff always makes me hyped!

The First step is to create a GitHub personal access token. Follow the official documents here for more information.

Next, Head over to your blog_source repository on GitHub and click Settings → Secrets → new repository secret. Paste the token you just got, set the name to ACTIONS_DEPLOY_KEY and hit the Add secret button.

Add the file .github/workflows/pages.yml below to your source code repository. The external_repository points to your blog repository.

name: hugo publish

    - main

    runs-on: macos-latest
    - name: Git checkout
      uses: actions/checkout@v2
        fetch-depth: 0
    - name: Update theme
      run: git submodule update --init --recursive

    - name: Setup Hugo
      uses: peaceiris/actions-hugo@v2
        hugo-version: '0.88.1'

    - name: Build
      run: hugo  --enableGitInfo --minify

    - name: Deploy
      uses: peaceiris/actions-gh-pages@v3
        personal_token: ${{ secrets.ACTIONS_DEPLOY_KEY }}
        external_repository: HuiGong-dev/
        publish_branch: main
        publish_dir: ./public
        user_name: HuiGong-dev

And we are done! GitHub Actions will do all the boring “build and deploy” routine while you can concentrate on content creating and more.

Happy blogging!

Update for GitHub Action and Lastmod

Recently I tried to show Last Update info based on last commit for each post and here is how:

  1. add enableGitInfo = true in your config.toml file.

  2. Ceate a new directory layouts/_default/(if not exists) directly under hugo directory and create a file called “single.html”.

cd your-hugo-directory
mkdir -p layouts/_default
cd layouts/_default
touch single.html
  1. Add content to single.html. Here is mine for reference:
{{ define "main" -}}
<div class="post">
  <h1>{{ .Title }}</h1>
  <time datetime={{ .Date.Format "2006-01-02T15:04:05Z0700" }} class="post-date">Published: {{ .Date.Format "Mon, Jan 2, 2006" }}</time>
  {{- if .GitInfo }}
  <time datetime={{ .Date.Format "2006-01-02T15:04:05Z0700" }} class="post-date">Updated: {{.Page.Lastmod.Format "Mon, Jan 2, 2006" }}</time>
  {{- end }}
  {{ .Content }}

{{ if .Site.DisqusShortname -}}
{{ template "_internal/disqus.html" . }}
{{- end }}
{{- end }}

Your single.html may look different to mine and it’s totally fine. The point is to use .Page.Lastmod to get the last update date.

  1. Add frontmatter to config.toml
  date = ['date', 'publishDate', 'lastmod']
  expiryDate = ['expiryDate']
  lastmod = [':git', 'lastmod', 'date', 'publishDate']
  publishDate = ['publishDate', 'date']

So that Hugo will try to get lastmod info from .GitInfo first.

  1. Add --enableGitInfo flag in the GitHub Action file and set checkout action fetch-depth to 0.

The Hugo official document said that you can use .GitInfo by either adding enableGitInfo = true in your config.toml file or add --enableGitInfo flag when calling hugo server but that’s not accurate. You need both to make it work. Otherwise, the .GitInfo object would be null.

By default, the GitHub checkout action only fetches the commit which triggered the workflow. The result is that all the posts will show the same last update date. Specifying fetch-depth:0 will solve this problem (You can ignore this part if you’re not using GitHub action).

The yml file for GitHub Action has been updated above.

What a ride!