I write in Emacs Org-mode. Hugo turns it into a website. ox-hugo bridges the two. Git versions it, GitHub Actions deploys it to S3.
That’s the whole stack. Here’s how to set it up.
What you need
- Hugo installed
- Git installed
- A GitHub account (for Actions later)
- Emacs with basic Org-mode knowledge
Init Hugo
Create the project
SITE_DIR="/Users/sz/myblog" # your actual path here
hugo new site $SITE_DIR
cd $SITE_DIR
git init
Optionally connect it to a remote: git remote add origin <your-repo-url>.
Add a theme
I use xmin – minimal, no JavaScript, gets out of the way.
cd $SITE_DIR
git submodule add https://github.com/yihui/hugo-xmin.git themes/xmin
mv hugo.toml hugo.toml_bak
cat > config.yaml << EOF
baseurl: "/"
languageCode: "en-us"
title: "SZ"
theme: "xmin"
permalinks:
post: "/posts/:year/:month/:day/:slug/"
menu:
main:
- name: Home
url: ""
weight: 1
- name: About
url: "about/"
weight: 2
- name: Categories
url: "categories/"
weight: 3
- name: Tags
url: "tags/"
weight: 4
params:
footer: "© [SZ](https://example.com) 2024 -- {Year}"
markup:
goldmark:
renderer:
unsafe: true
EOF
Write a test post
cd $SITE_DIR
hugo new content content/posts/my-first-post.md
cat > content/posts/my-first-post.md << EOF
+++
title = "My First Post"
date = 2024-11-30T00:16:00-08:00
tags = ["website"]
categories = ["TECH"]
draft = false
+++
## Introduction
This is **bold** text, and this is *italicized* text.
Here's a [link](https://example.com) for reference.
EOF
Run it
hugo server --port 1111
Hit http://localhost:1111/. You should see a site. It’s ugly. That’s fine – we’re not here for the CSS.
Wire up ox-hugo
Install
M-x package-install RET ox-hugo RET
ox-hugo takes your Org-mode content and exports it as Markdown that Hugo understands. You write in Org, Hugo renders HTML. The two never meet directly.
Pick a structure
ox-hugo supports two modes:
| Mode | How it works | Good for |
|---|---|---|
| Subtree-based | Multiple posts in one .org file, each as a subtree |
Small to medium blogs |
| File-based | One .org file per post |
Larger blogs |
We’ll use subtree-based here. One file, many posts.
Create a post
cat > example-post.org << 'EOF'
#+hugo_base_dir: /Users/sz/myblog
;* Posts
;** DONE My First Hugo Post
:PROPERTIES:
:EXPORT_FILE_NAME: my-first-hugo-post
:EXPORT_DATE: 2024-07-12T17:05:38-04:00
:EXPORT_TAGS: blog, emacs
:EXPORT_CATEGORIES: TECH
:END:
This is a sample post created using ox-hugo and Emacs Org-mode.
EOF
A few things to note:
#+hugo_base_dirpoints to your Hugo site root. Exported Markdown lands in<base_dir>/content/posts/.EXPORT_FILE_NAMEis required on each post subtree. It becomes the Markdown filename.- Tags and categories are optional. See the ox-hugo docs for details.
Export
Place your cursor inside the subtree you want to export:
C-c C-e H H
Check content/posts/ for the exported Markdown. Reload Hugo. Your post is live.
That’s the writing loop
Org-mode to ox-hugo to Hugo. Write, export, preview. Part 2 covers getting it deployed – GitHub Actions and S3 – so you’re not manually uploading HTML like it’s 2003.