How to build static websites using org-mode, hugo and github actions.
Table of Contents
There’s no shortage of website building frameworks, however this post is for the stubborn and maybe the lazy—who’d like to write everything in org-mode
and automate the rest. What follows is a practical (though not optimal) way of building a static website using Org-mode, Hugo, and GitHub Actions.
The goal with the post is just to demonstrate that it’s possible to publish a website without leaving emacs, not to find an optimal way.
We will explore the idea of using hugo and org-mode
to quickly build websites, and then deploy them to GitHub Pages using GitHub Actions.
Assumptions
This guide assumes you’re familiar with Emacs, Git, and some basics of Hugo and GitHub Actions. It also assumes you’re comfortable installing dependencies. There are plenty of resources online to help you get started.
Structure
There is, of course, not just one way to organize your project. However, here’s a structure that works well when integrating with Org-mode.
.
├── content
│ ├── config
│ │ └── _default
│ │ └── Your hugo configs goes here.
│ ├── hugo.toml ;; Your project specific configs
│ ├── org
│ │ ├── index.org
│ │ └── org files goes here.
│ └── themes
│ └── theme-name ;; As submodule.
├── LICENSE
└── README.org ;; Imagine using markdown ;)
Follow your theme’s instructions for setup. As long as you place your .org
content in the org
directory, you’ll be able to export everything you need.
Hugo
Start by picking a theme, for example by exploring https://themes.gohugo.io.
Unless you plan to modify the template, adding the theme as a submodule is recommended.
git submodule add -b main https://<THEME_URL>.git themes/<THEME_NAME>
Next, configure content/hugo.toml
:
languageCode = 'en-us'
title = 'demo'
theme = "archie" # For example
Theme-specific options often live in ./content/config/_default/
. You can edit for example params:
# ./content/config/_default/params.toml
# These params might be generic or theme specific.
favicon = "images/favicon.png"
#..
#..
# E.g. theme specific Announcement
# https://github.com/gethugothemes/hugo-modules/tree/master/components/announcement
[announcement]
enable = true
expire_days = 7
content = "The grass is not greener on the other side."
How to build ?
There are multiple ways to build a Hugo site. The simplest is using the Hugo CLI: https://gohugo.io/commands/hugo_build/. We’ll also look at how to build and deploy with GitHub Actions.
Org-mode
Now that we’ve set up and tweaked the site, let’s start adding content.
Prerequisites
Exporting content
Here’s how to create the homepage with a _index.md
:
#+title: Index
#+HUGO_BASE_DIR: ../content/
#+HUGO_SECTION: /
#+EXPORT_FILE_NAME: _index
Recognize untruth as a condition of life...
Export using Emacs:
(org-hugo-export-wim-to-md t)
This will create ./content/content/_index.md
. Yes, the naming should maybe be reconsidered, but it works for our demonstration.
Custom Front Matter
Need theme-specific front matter (e.g., YAML)? You can use the hugo_front_matter_format:
property:
#+title: Index
#+HUGO_BASE_DIR: ../content/english
#+HUGO_SECTION: /
#+EXPORT_FILE_NAME: _index
#+hugo_front_matter_format: yaml
\\#+begin_src yaml :front_matter_extra t
banner:
title: "Demo"
button:
enable: true
\\#+end_src
Automating with GitHub Pages
Step 1: Checkout Repo and Setup GitHub Pages
name: Publish Changes
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
HUGO_ENV: production
GO_VERSION: "1.23.3"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
Step 2: Export Org Files on the Runner
We clone the necessary Emacs packages and run Emacs in batch mode.
- name: Clone dependencies
run: |
git clone https://github.com/kaushalmodi/ox-hugo.git ox-hugo
git clone https://github.com/kaushalmodi/tomelr tomelr
- name: Setup emacs
uses: purcell/setup-emacs@master
with:
version: 28.2
- name: Export Org file to Markdown
run: |
yes | emacs ./content/org/index.org --batch -L $(pwd)/tomelr -L $(pwd)/ox-hugo -l $(pwd)/ox-hugo/ox-hugo.el --eval='(org-hugo-export-wim-to-md t)' --kill
Want to process all .org
files?
- name: Export Org files to Markdown
run: for f in ./content/org/*.org; do emacs "$f" --batch -L "$(pwd)/tomelr" -L "$(pwd)/ox-hugo" -l "$(pwd)/ox-hugo/ox-hugo.el" --eval='(org-hugo-export-wim-to-md t)' --kill; done
Step 3: Run Hugo
A standard Hugo build step:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.125.0'
extended: true
- name: Build with Hugo
working-directory: ./content
env:
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: |
hugo \
--minify \
--baseURL "${{ steps.pages.outputs.base_url }}/"
Alternative with npm:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.141.0'
extended: true
- name: Install Go
run: |
wget -O ${{ runner.temp }}/go.deb https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz \
&& sudo tar -C /usr/local -xzf ${{ runner.temp }}/go.deb
- name: Install npm dependencies
run: npm --prefix ./content install
- name: Build site
run: npm --prefix ./content run build
Step 4: Upload and Deploy to GitHub Pages
- name: Upload artifact
id: upload-artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./content/public
deploy:
if: ${{ github.event_name == 'push' }}
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Closing Thoughts
This setup isn’t the most minimal nor optimal, but it works quite well. You have the ability to write your entire website in Org-mode, export it using ox-hugo
, and deploy everything with a single push to GitHub.
The approach is (for me) quite enjoyable, as once setup, I can quickly add an entry to the website without leaving emacs.
If you’re allergic to clicking through CMS dashboards and want to spend time writing content, this method might be something for you.
The grass isn’t greener - the weeds just grow differently. Choose the problems you enjoy solving.