Publishing a Website from Emacs and Hugo


After 5 years, it’s time to give the site a bit of a refresh, now with fewer images and more words. Previously I used bootstrap plus a bit of manual editing. This time I’ll be using a pipeline of Emacs org-mode -> ox-hugo -> hugo -> This post will self-document my steps to get all that up and running. The last time I did any web-related things was over 5 years ago, and I wasn’t an expert then, so these steps should be taken with a grain of salt.

Hugo Setup

sudo snap install hugo
mkdir petercheng && cd petercheng
hugo new site petercheng

Emacs init:

(use-package ox-hugo
    :ensure t
    :after ox)

Set up a theme (I’m using the hyde-hyde theme)

git submodule add themes/hyde-hyde


For my intended setup, there are only 2 files I’ll be working with. The first one is config.toml, which stores global hugo settings, as well as parameters for my chosen theme. I’m not really sure how to find all the toggle-able parameters for a given theme besides digging through the theme code or looking at example sites.

As an early example of why I’m using org-mode, I can directly insert a live copy of my config.toml file below, simply by including the line:

#+INCLUDE: "config.toml" src ini

baseURL = ""
title = "Peter Cheng"
theme = "etch"
languageCode = "en-US"
enableInlineShortcodes = true
pygmentsCodeFences = true
pygmentsUseClasses = true

    description = "Peter Cheng's Website"
    dark = "auto"
    highlight = true

    identifier = "posts"
    name = "posts"
    url = "/"
    weight = 10

    identifier = "about"
    name = "about"
    url = "/about/"
    weight = 20

  posts = "/:title/"

  # Allows HTML in Markdown
  unsafe = true

One early roadblock I hit was that hyde-hyde uses highlight.js for syntax highlighting, which does not contain emacs-lisp as a language option, unlike org-mode and chroma (hugo’s default syntax highlighter). I’m currently using lisp as a compromise, and it took me a while to realize that highlightjslanguages needed to be set to include non-default languages in highlight.js. If an unsupported (or empty!) language is passed to highlight.js, at least with hyde-hyde, it results in poorly formatted output, which led to much confusion for a while.

The other file I need to create is the org file that generates all this content, on every page, following ox-hugo’s single-page architecture. In normal Hugo, individual pages written in markdown (or now in org-mode) are placed inside the content directory inside the project root. With ox-hugo, a single org-mode file can be used to generate all pages, posts, and any other content. This has some advantages in allowing usage of org-mode functionality, as well as re-use of content or property settings across pages.

There’s a number of hugo properties that can be set within the file, but the only required one is HUGO_BASE_DIR, which specifies the root directory of the hugo website, relative to the org file.


Afterwards, I have 2 top-level sections in my org file, Pages, and Posts. Any properties set under a section will be applied to subsections, so I have the following properties set for each, to place pages at the top level of my exported files, and posts within a subdirectory.

* Pages
* Posts

I can then create pages or posts by creating subsections within the relevant section. The EXPORT_FILE_NAME property is required to be set for each, which determines the exported filename. Here’s an example of the properties setting for this current post.

** Publishing a Website from Emacs and Hugo
    :EXPORT_FILE_NAME: website-v2-setup
    :EXPORT_DATE: 2018-06-04


Ox-hugo adds a new export option to the org-mode export menu. (C-c C-e) by default. There’s a few options for exporting, but currently I find it simplest just to always export all content, with (C-c C-e H A). One setting I’ve seen used a lot is #+HUGO_AUTO_SET_LASTMOD: t, and that doesn’t play nicely if always updating all files. But I don’t feel a need to track and update dates on every edit.

After exporting, markdown files should be created in the content directory, and hugo will auto-reload pages if already running (to start hugo, run hugo server from the base directory).

Getting Online

There are some fancy options for deploying, such as this guide, which demonstrates hugo publishing on a remote server, triggered by git post-receive. For the time being I’m going to keep thing simple, and simply use a script to generate a static site, which I’ll keep synced up via rsync. A final example of showing a live code view of my publishing script:

#!/usr/bin/env bash

set -eux


rm -rf public
# rsync is problematic on WSL (
# --whole-file is one workaround
# Not being able to use -z is another limitation
rsync -avh --whole-file --progress --delete public/ "$username@$server:"