How to Set Up a Blog for Free Powered by Hugo, Firebase, and GitHub
How to set up a fast, free, and low-maintenance blog powered by Hugo, Firebase Hosting, and GitHub Actions—based on the exact steps I took to build this site.
What is Hugo?
Hugo is one of the fastest open-source static site generators available. Instead of querying a database and rendering pages on the fly on every request like traditional CMS platforms, Hugo takes your Markdown files, applies HTML templates, and compiles your entire site into static HTML, CSS, and JavaScript in milliseconds.
Even better: Hugo is written in Go (Golang), which is currently my favourite programming language! Because it’s built in Go, Hugo ships as a single, dependency-free binary with blazing-fast build times and rock-solid tooling. If you’re a fellow Go fan—or curious to see what a large, high-performance real-world Go codebase looks like—I highly recommend checking out the Hugo source code on GitHub.
1. Initializing the Site & Adding a Theme
Once you have Hugo installed, scaffolding a new site and initializing Git takes just a few commands:
hugo new site blog
cd blog
git init
Rather than copying theme files directly into the project, adding your theme as a Git submodule makes it easy to pull upstream updates later without cluttering your repository history. For this blog, I used the Rusty Typewriter theme:
git submodule add https://github.com/math-queiroz/rusty-typewriter themes/rusty-typewriter
2. Configuring hugo.toml and Client-Side Search
Your site’s global settings live in hugo.toml. Beyond setting your baseURL, title, and author details, this is where you configure menus, taxonomies, and theme parameters.
Rusty Typewriter also includes built-in client-side search powered by FlexSearch. To enable it, you configure Hugo to output a searchindex.json file at build time alongside the standard HTML and RSS outputs.
Here is the complete hugo.toml configuration:
baseURL = 'https://www.tangent-labs.co.za/'
locale = 'en-US'
title = 'Off on a tangent'
theme = 'rusty-typewriter'
[rusty_typewriter]
[params.rtwt]
sidePane = true
countPageItems = 7
searchPath = 'search'
[params.rtwt.home]
showBio = true
showAuthorImg = false
sidePaneTags = true
showFeatured = true
showRecent = true
hideRecentWhenFeatured = true
countRecent = 5
[params.rtwt.side.home]
sidePaneSticky = false
taxonomies = ['tags']
countTaxonomy = 6
[params.rtwt.side.list]
sidePaneSticky = false
[params.rtwt.side.single]
sidePaneSticky = true
showDetails = true
showTableOfContents = true
showAttachments = true
showRelated = true
countRelated = 5
[related]
includeNewer = true
[[related.indices]]
name = 'tags'
weight = 100
[taxonomies]
tag = 'tags'
[params]
summaryLength = 1
dateFormat = '2 Jan 2006'
searchLimit = 10
[params.author]
name = 'Keanu Dirkse'
[params.links]
hugo = 'https://gohugo.io/'
theme = 'https://github.com/math-queiroz/rusty-typewriter'
[[menus.main]]
name = 'Posts'
pageRef = '/posts'
weight = 1
[[menus.main]]
name = 'About'
pageRef = '/about'
weight = 2
[outputFormats]
[outputFormats.SearchIndex]
mediaType = 'application/json'
baseName = 'searchindex'
isPlainText = true
notAlternative = true
[mediaTypes]
[mediaTypes.'application/json']
suffixes = ['json']
[outputs]
home = ['HTML','RSS','SearchIndex']
Tip on Go Date Formatting: Notice
dateFormat = '2 Jan 2006'. Because Hugo uses Go’stimepackage under the hood, layouts are formatted using Go’s reference time (Mon Jan 2 15:04:05 MST 2006) rather thanYYYY-MM-DDtokens.
Home Page Data & Static Pages
For the Rusty Typewriter home page bio, create data/rtwt/content.yaml:
home:
greetings: 'Off on a tangent'
text: 'Notes and experiments from Tangent Labs.'
And create an About page at content/about.md (make sure to include a date in the front matter so the search index doesn’t fall back to 1 Jan 0001):
+++
title = 'About'
date = 2026-11-11T18:00:00+02:00
type = 'blank'
+++
# About
Hi, I'm Keanu Dirkse. This is where I write about things I'm building and learning.
3. Setting Up .gitignore
Before committing or setting up hosting, make sure build artifacts, caches, and any local credentials or service account keys are ignored in .gitignore:
public/
resources/
.hugo_build.lock
# Firebase
.firebase/
*-debug.log
firebase-debug.log*
.runtimeconfig.json
# Secrets & credentials
.env
.env.*
!.env.example
*.pem
*.key
*.p12
*.pfx
*service-account*.json
*serviceAccount*.json
*-firebase-adminsdk-*.json
google-services.json
GoogleService-Info.plist
.netrc
# Dependencies / logs
node_modules/
npm-debug.log*
# OS / editor
.DS_Store
.vscode/
.idea/
*.swp
4. Firebase Hosting Configuration
To host the static site on Firebase Hosting, run firebase init hosting:
- Public directory:
public(where Hugo outputs compiled files) - Configure as a single-page app (rewrite all urls to /index.html)? No. Hugo generates a separate HTML file for every route. Enabling SPA rewrites would route missing URLs to the home page instead of your
404.html. - Overwrite
public/404.htmlorindex.html? No.
This generates two configuration files in your repository:
firebase.json:
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
.firebaserc:
{
"projects": {
"default": "<YOUR_FIREBASE_PROJECT_ID>"
}
}
5. Automating Deploys with GitHub Actions
Instead of building and deploying manually from your machine, you can use GitHub Actions to build and deploy automatically whenever changes are pushed to main.
Setting Up the Secret
- In the Firebase Console, go to Project settings → Service accounts and generate a new private key JSON (or run
firebase init hosting:github). Never commit this JSON file to Git. - In your GitHub repository, go to Settings → Environments, create an environment (e.g.
main-blog), and add an environment secret namedFIREBASE_SERVICE_ACCOUNTcontaining the service account JSON.
The Workflow File
Create .github/workflows/deploy.yml:
name: Deploy to Firebase Hosting
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy
cancel-in-progress: true
jobs:
deploy:
# Only ever deploy from main (also guards manual runs)
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: main-blog
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.169.0'
extended: true
- name: Build
run: hugo --gc --minify
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
projectId: <YOUR_FIREBASE_PROJECT_ID>
channelId: live
Notice submodules: recursive on the checkout step—without it, GitHub Actions won’t pull the theme submodule and the build will render an empty site.
6. Managing the Blog Going Forward
With the CI/CD pipeline in place, day-to-day blogging is just writing Markdown and pushing to Git.
Writing a New Post
Create a new post using the default archetype:
hugo new posts/my-post-title.md
This creates content/posts/my-post-title.md with TOML front matter:
+++
title = 'My Post Title'
date = 2026-11-13T11:00:00+02:00
draft = true
tags = ['golang', 'notes']
+++
Previewing Locally
Run Hugo’s built-in development server with live reload:
hugo server -D
- Open
http://localhost:1313in your browser. -D(--buildDrafts) includes posts withdraft = true.-F(--buildFuture) includes posts whosedatetimestamp is still in the future.
Publishing
When your post is ready to go live:
- Change
draft = truetodraft = falsein the post’s front matter (and make sure thedateis not set in the future). - Commit and push to
main:GitHub Actions will automatically build the site withgit add content/posts/my-post-title.md git commit -m "Publish post: My Post Title" git pushhugo --gc --minifyand deploy it to Firebase Hosting.
Cloning on a New Machine & Updating the Theme
- Cloning onto a new machine: Because the theme is a submodule, clone with
--recurse-submodules, or run:git submodule update --init --recursive - Updating the theme: To pull the latest changes from the theme author:
git submodule update --remote themes/rusty-typewriter git add themes/rusty-typewriter git commit -m "Update rusty-typewriter theme"
References & Further Reading
- Hugo Official Documentation — Comprehensive guide to Hugo’s directory structure, content management, and CLI commands.
- Hugo Source Code on GitHub — Explore how Hugo is architected in Go.
- The Go Programming Language — Official Go documentation, tutorials, and standard library reference (including
timelayout formatting). - Hugo Template Lookup Order — How Hugo resolves layouts and allows safe theme overrides.
- Rusty Typewriter Theme — Theme repository and configuration documentation.
- Firebase Hosting Documentation — Custom domains, caching headers, and CLI reference.
peaceiris/actions-hugo&FirebaseExtended/action-hosting-deploy— The GitHub Actions used in our deployment pipeline.- Git Tools: Submodules — Official Pro Git chapter on working with submodules.