This post is a comprehensive guide to creating and maintaining a Hugo blog using Org mode in Doom Emacs. We’ll cover:
- Installing Hugo and a theme
- Configuring
ox-hugo
- Structuring content with Org mode
- Troubleshooting common issues
- Deploying your site
If you’re using Emacs, Hugo, and Git, this guide will help you **get up and running smoothly**—without running into the same issues I did!
Pre-requisites
To follow along, ensure you have:
- Doom Emacs installed (or plain Emacs with Org mode).
- The
+hugo
flag enabled in Doom’sinit.el
:(org +roam2 +hugo) ; Enable Hugo support
ox-hugo
installed (for Org to Hugo exports).- A Git repository initialized in your blog directory to enable Hugo’s
GitInfo
feature.
Caveat: If you want Git-based last modified dates to work, ensure your blog directory is inside a Git repo.
Installing Hugo and Setting Up the Blog
Install Hugo
📌 Important: You must install Hugo extended version (v0.123.0+), or you’ll run into errors related to .Site.Lastmod
and SCSS processing.
On Ubuntu, download the DEB manually instead of using apt
:
wget https://github.com/gohugoio/hugo/releases/download/v0.144.1/hugo_extended_0.144.1_linux-amd64.deb
sudo dpkg -i hugo_extended_0.144.1_linux-amd64.deb
hugo version # Verify that it prints v0.144.1 (extended)
If you want to deploy via hugo deploy
you will need to download this version instead.
wget https://github.com/gohugoio/hugo/releases/download/v0.144.2/hugo_extended_withdeploy_0.144.2_linux-amd64.deb
sudo dpkg -i hugo_extended_withdeploy_0.144.2_linux-amd64.deb
hugo version # Verify that it prints v0.144.2 (extended)
Caveat:
If you install Hugo via apt
, you may get an outdated version that lacks necessary features.
Initialise Hugo
I store my blog inside my “second brain” at ~/brain/blog
. Run:
mkdir -p ~/brain/blog
cd ~/brain/blog
Once in here we can create a new site. The new site will have the correct structure but no content or theme yet.
hugo new site .
Add a Theme
I full list of themes can be found here here.
I use the hugo-theme-stack theme, installed as a Git submodule for customization:
git submodule add https://github.com/CaiJimmy/hugo-theme-stack.git themes/hugo-theme-stack
📌 Alternative: You can install it as a Hugo module, which simplifies version management, but I prefer submodules for direct theme customization.
Configuring Hugo
Edit your config.toml
file to set up the site:
baseURL = "http://teleoplexy.com/"
languageCode = "en-uk"
title = "Teleoplexy"
theme = "hugo-theme-stack"
enableGitInfo = true
Then we want to do some theme specific configurations. We’ll start with the [params]
configuration:
[params]
mainSections = ["posts"] # IMPORTANT: Must match your content folder name
featuredImageField = "image"
rssFullContent = true
favicon = "/favicon.ico"
description = "Your page description here."
[params.sidebar]
compact = false
subtitle = "This description appears in the left sidebar."
[params.sidebar.avatar]
enabled = true
local = true
src = "img/me.jpg"
[params.article]
toc = true
readingTime = true
Note: This configuration will differ if you have chosen a different theme and it is best to consult their docs.
This is how we configure the navigation in the side bar.
# Menu
[menu]
[[menu.main]]
name = "Home"
url = "/"
weight = -90
identifier = "home"
[menu.main.params]
icon = "home"
[[menu.main]]
name = "Posts"
url = "/posts/"
weight = -80
identifier = "posts"
[menu.main.params]
icon = "archives"
[[menu.main]]
name = "About"
url = "/about/"
weight = -70
identifier = "about"
[menu.main.params]
icon = "info-circle"
[[menu.main]]
name = "Tags"
url = "/tags/"
weight = -60
identifier = "tags"
[menu.main.params]
icon = "tag"
[[menu.main]]
name = "Categories"
url = "/categories/"
weight = -50
identifier = "categories"
[menu.main.params]
icon = "categories"
To add the social links we add this:
# Social Links
[[menu.social]]
name = "GitHub"
url = "https://github.com/jackmarsh"
weight = 1
identifier = "github"
[menu.social.params]
icon = "brand-github"
[[menu.social]]
name = "X"
url = "https://x.com/0xJackMarsh"
weight = 2
identifier = "X"
[menu.social.params]
icon = "brand-x"
📌 Fix for Missing Icons:
If Hugo logs “icon not found” errors, add missing icons to themes/hugo-theme-stack/assets/icons/
. You can find similar looking icons for this theme from tabler.
Writing a Post in Org Mode
All Posts
I create a file under ~/brain/blog/org
called all_posts.org
. This org file will contain all the properties required by ox=hugo
for each of the blog posts.
#+HUGO_BASE_DIR: ../
The #+HUGO_BASE_DIR:
and :EXPORT_FILE_NAME:
property determine where the markdown files are placed. Typically this is in content/posts
. See below.
Adding first post
I like to keep each post in it’s own subdirectory so that I can keep pictures and content relevant to this specific post in the same sub dir.
Inside ~/brain/blog/org/first_post/index.org
you can write all the content for your first post.
* My first header
Some content in my first post.
Then we pull that into all_posts.org
like so.
#+HUGO_BASE_DIR: ../
* DONE My First Post :tag1:@category1:
:PROPERTIES:
:EXPORT_FILE_NAME: my-first-post
:EXPORT_HUGO_TYPE: post
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :description "An introduction to my first post."
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :image "/images/my-first-post.png"
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :comments true
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :license "CC BY-NC-SA 4.0"
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :math false
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :toc true
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :keywords ["blog" "hugo" "ox-hugo"]
:END:
#+INCLUDE "first_post/index.org"
These properties set the hugo front matter, some of which is needed by the theme I’ve chosen.
Export
With your cursor anywhere on this subheading, export with:
C-c C-e H H
or,
M-x
then org-hugo-export-wim-to-md
Development Server
To view these changes and make sure everything is working as expected navigate to your terminal and run:
$ hugo serve
Watching for changes in ~/brain/blog/{archetypes,content,data,layouts,static,themes}
Watching for config changes in ~/brain/blog/config.toml, ~/brain/blog/themes/hugo-theme-stack/config.yaml
Start building sites …
hugo v0.144.1-a79d63a44659b6bc76dcdf223de1637e0bd70ff6+extended linux/amd64 BuildDate=2025-02-18T12:14:07Z VendorInfo=gohugoio
| EN
-------------------+-----
Pages | 30
Paginator pages | 0
Non-page files | 0
Static files | 2
Processed images | 1
Aliases | 10
Cleaned | 0
Built in 84 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
You can then open the Web Server which is available on http://localhost:1313.
Publishing The Site
In this step I publish the site, but do not deploy it.
When publishing a site, Hugo create the entire static site in the public
directory in the root of the project. This includes HTML files, and assets such as images, CSS files and JavaScript files.
To publish the site, simply run:
hugo
Deploying The Site
I’ve deployed the site with Google Cloud Storeage bucket behind a load balancer.
I just followed the steps here.
After setting up the bucket and load balancer and adding this to the config.toml
:
[deployment]
[[deployment.targets]]
name = "production"
URL = "gs://<YOUR-BUCKET-NAME>"
You can then just run:
hugo deploy [--target=<target name>]
Adding new posts
From here on out to add new posts we simply:
- Write the post under
~/brain/blog/org/<new_post>/index.org
- Add the post, with its properties, to
~/brain/blog/org/all_posts.org
- When finished, set it to
DONE
withC-c C-t d
- When finished, set it to
C-c C-e H H
orM-x
thenorg-hugo-export-wim-to-md
- Then:
cd ~/brain/blog hugo hugo deploy
Conclusion
We covered:
- ✅ Setting up Hugo with Org mode
- ✅ Avoiding common pitfalls
- ✅ Fixing missing archives & JSON issues
- ✅ Deploying the site
🚀 Now you have a fully working, Hugo-powered Emacs blog!