Publishing your own website with Emacs and hugo

I wanted to present some things to the public. To do so, a personal blog and a static website is the weapon of choice (maybe 15 years to late). And what is a good start for a personal blog? A post about the setup and workflow of the blog itself. I start with the prerequisites for a personal website and afterwards explain the usage of Emacs org-mode and hugo for blogging. I expect some technical understanding and the will to google.


There are many ways to publish your own site but the fundamental things are a domain and public available webspace. I got my domain from godaddy which are cheaper than some other providers. Don’t mix up the upfront costs (You pay the first year) with the long-time costs (You pay yearly). A normal domain costs between one and two euro a month.

For the webspace I use uberspace which is a shared webhoster with a good pricing model and easy customization through the ssh access and many detailed description. Why a shared webhoster and not a root server? I have my own NAS at home which can serve the same purpose but I don’t have a static IP address until I pay more for my ISP. I decided against a root server (e.g. IONOS) because the setup of e-mail, certificates and some other basic things are doable (if you’re experienced enough) but tedious and one underestimates the regulary maintenance time.

So we’ve the most important parts: a Domain and a Webspace. Now point set a dns record for your domain which points to your webspace and the prerequisites are done. Next, we can go one step further and talk about using Emacs org-mode for your new website.

Using Emacs org-mode for building your own website

First, why emacs? I’m using emacs since 8 years for coding, note taking and some other things. So using emacs for website building was naturally to me. The org-mode from emacs is one the best organization systems I’ve seen in the wild. Well it doesn’t have a fancy webapp or shiny buttons but it is plain text and can be edited and exchanged within every operating systems. It allows code to live alongside its documentation and has many more outstanding features. So check out yourself.

The worg (Community driven manuals) provides a list of framework for publishing. The range lasts from using the internal capabilities to using static-site generators or other tools written in other languages.

To choose the correct framework we need to know what we want to do:

  • Maintain static websites alongsite the blogroll (e.g. About, Main, Impressum)
  • Have an easy system to publish new blog post with Date and Tags
  • Let the user comment on blog posts
  • Maybe maintain some sort of public wiki which are not real blog posts (but this is something for the future)
  • Preview posts without publishing them
  • Traffic analysis

Let’s see what frameworks exists. Follow the links if you want more details about the frameworks.

Name Last Udpate Description
o-blog 7 years ago Stand-alone system in org but outdated and archived.
org-jekyll current Uses Jekyll for publishing and org project with hand-made conversion rules.
Projects current The internal publishing management system of org.
Blorgit 9 years ago Based on Ruby for publishing exported org files.
org2blog current Uses Wordpress for publishing org-files.
org-page 4 years ago The project is unmaintained.
lazyblorg current Written in Python and build for simplicity.
ox-hugo current Uses Hugo for publishing with ox-hugo as exporter for org files.

These are not all frameworks but it gives a good overview of the used approaches. Search for yourself for other frameworks since they come and go or write something yourself. Elisp is right under your finger-tips. ;)

I would prefer a system under active development and I dislike Wordpress from a personal perspective. This leaves us with org-jekyll, Projects, lazyblorg and ox-hugo. Projects is the internal publishing software for emacs and I think it requires the most manual configration. lazyblorg sounds nice but is under development and some general features like comment section or analysis are more complicated to get to work. This keeps me away from using this at the moment. This leaves us with org-jekyll and ox-hugo. Both depends on well-known static-site generators with many feature. I would prefer ox-hugo simply from the fact that it has a better documentation than the org-jekyll project. So, let’s setup hugo and ox-hugo.


To get this up and running we need hugo and emacs running somewhere. My shared hosting provder allows many applications on the backend site which includes hugo. This means I could write my blog on my local machine let emacs convert the org-files and then upload them to he hosting provider. There hugo does the static-site generation and it gets served. I found this type of workflow has some drawbacks. First, if I switch my hoster and the new one only allows static files (like the classic ones), it would break the workflow. Second, I like some sort of preview step before publishing something to the wild. Running a local hugo instance and only uploading the generated files seemed to me a better controlled workflow. Additionally, this allows a tighter integration between emacs and hugo which we see later. It’s time to setup hugo.


Hugo can be installed on many distributions from the package manager (e.g. pacman -S hugo). Another option is to grab the binary directly from Github.

Download the latest version with:

2tar xvf hugo_0.87.0_Linux-64bit.tar.gz hugo

Place the binary located in hugo in your directory of choice. If you choose the manual way of installation check the github page of hugo for the latest release.

Afterwards, I followed the quickstart guide from the hugo documentation and created a new website and added a theme.

 1# create a new site and set it under git
 2hugo new site
 4git init
 5# add the default theme
 6git submodule add themes/cactus-git
 7cp themes/cactus-git/exampleSite/config.toml .
 8# Change the theme setting in config.toml to cactus-git
 9# Create the first post
10hugo new posts/
11# Test setup by starting the local hugo server with drafts included
12hugo server -D
13# See the live page under http://localhost:1313

The last line should start the server with only warnings and no errors. The new website can be found under http://localhost:1313.

To build the final website run hugo in the website directory or hugo -D if your want draft pages to be included. The result ends up in the public folder of the website. In my case this is ~/

To deploy the site to my shared hoster I use rsync with:

1rsync -avzz --delete ~/<path-to-site>/public/ <user>@<hostname>:/<document-root>/

Depending on the hoster other options than ssh must be used. Now you should see the website served by your domain.

If this succeds, you can start changing your website further and make it more of your own one. A good starting point is the config.toml. If your theme provides an exampleSite copy the config file and start adjusting it to your needs.

Some other possible adjustments are:

  • Use another theme
  • Configure your theme
  • Create content, e.g. “about”, “interests”, “a post about your setup”

If you only wanted to know something about hugo, you can leave now and skip the emacs part but this is the more interesting one.


Now we have hugo running on your local machine creating static websites and we manually upload the generated website to your hoster. Next is to setup emacs with ox-hugo. ox-hugo has an extensive documentation which you can refer if stuck.

I use use-package for managing my emacs configuration. So the I paste the following into my init-file and hit C-x C-e to evaluate the code. [Ref]‚Äč

1(use-package ox-hugo
2    :ensure t
3    :after ox)

This install the package ox-hugo and loads it into emacs scope. The package provides two types of writing styles with org-mode. One can either have one org-file for each post or one org-file where each subtree can be a blog post.

I prefer the mixed style at the moment and created a new directory for the blog within my org folder (mkdir ~/org/website). For blog posts I use an org file called where the posts resides in the org subtree and for special sites I use a single file.

I use a private Nextcloud instance to backup my org-files to another machine thus the original content of our new website is already save. Ensure some sort of backup for the hugo stuff without the /public folder.

Frontmatter and Properties

Hugo is a static site generator which uses a fixed directory layout with special meanings assinged to the folders. The content generated by emacs gets converted from org to Markdown and resides under <website-name>/content/. The other special folders are:

  • archetypes: Stores templates when generating new content.
  • assets: Stores all files which are preprocessed by hugo before website creation.
  • config: Can be used to store additional configuration files used by hugo.
  • data: Stores configuration files used by hugo when generating the website.
  • layout: Stores .html files that specify how parts of the content gets rendered, for example list pages, homepage.
  • static: Location of all static content. It gets copied into the final side as-is.
  • resources: Some sort of cache directory.
  • themes: The theme template for the current websites. It is possible to store multiple themes here.

Most of the directories are only useful if a more complex website is needed.

Hugo uses Markdown files with a frontmatter to generate the correct html page. The frontmatter consists out of properties which are evaluated either during creation or processing of the Markdown file. Emacs org-mode provides properties to store advanced and metainformations for special processing. A good overview about emacs properties and the possibillites can be found here.

So, create your first “real” post with C-x C-f and point to ~/org/website/

Paste the following properties at the beginning of the buffer and save it.

 2#+HUGO_SECTION: posts
 7* TODO Publishing your own website
 9,:EXPORT_FILE_NAME: 2021-09-01-blogging-with-org
10,:EXPORT_DATE: 2021-09-01

This tells ox-hugo the location of the exported file and its name. The hugo section corresponds to the hugo archetypes and the base dir to the website base dir. The post can now be exported with C-c C-e H H and the Markdown file is written to your posts directory.

One can toggle the draft state of a post with the classic emacs TODO and DONE state. Than the done date is taken instead of the creation date.

Additionally, I followed the hint from the documentation and added the following to the website config.toml:

2  unsafe = true


To get support for tags or categories add to the post file header #+filetags: emacs hugo. Now the tags cann be applied to the subtrees with C-c C-q.

Categories can be defined in the same way with a @ as prefix like #+filetags: @coding

Polish and release

After the last part you can release whatever and whenever you want but there are some things to make your life easier.


ox-hugo provides an auto export mode to speed up the workflow.

I only use this setup in posts file and appended to the end of the file the following:

1* COMMENT Local Variables                          :ARCHIVE:
2,# Local Variables:
3,# eval: (org-hugo-auto-export-mode)
4,# End:

Now, when you save your file it gets directly exported.

Emacs workflow

I further tweaked my workflow with the following helpers:

Shortcut to open files directly

 1(defun jj-find-website-file ()
 2  "Find a file in the website directory."
 3  (interactive)
 4  (if (functionp 'counsel-find-file)
 5      (counsel-find-file "~/org/website")
 6    (find-file "~/org/website")))
 8;; I use general.el with custom mappings
 9(general-def 'jj-open-keymap
10  :wk-match-keys nil
11  "b" '(jj-find-website-file :wk "open site"))

Generate the static website

1(defun jj-run-hugo ()
2  "Generate a static site using hugo without drafts."
3  (interactive)
4  (let ((default-directory "~/")
5        (buffer (get-buffer-create "*hugo*")))
6    (apply 'start-process "hugo" buffer "hugo" '())
7    (switch-to-buffer-other-window buffer)
8    (special-mode)))

Sync the generated site

1(defun jj-sync-website ()
2  "Synchronize the generated website with the hoster."
3  (interactive)
4  (let ((buffer (get-buffer-create "*rsync*")))
5  (apply 'start-process "rsync" buffer "rsync"
6         '("-avzz" "--delete" "--exclude='matomo'" "~/"
7           "uberspace:/path/to/documentroot/"))
8  (switch-to-buffer-other-window buffer)
9  (special-mode)))

Replace paths used in the functions with your own ones. You can tweak the functions further if you like.

Matomo a libre Google Analytics alternative

Now, we’ve a running website with emacs integration. As a last step I want to know how many people are accessing my new website. Google analytics may be the platzhirsch but I prefer open-source tools. So, I decided to use Matomo which is a open-source and user-friendly alternative to Google analytics.

First we need a Matomo instance up and running. Uberspace provides a short tutorial.

1cd <document-root>

Now, open the website <your-domain>/matomo and follow the installation steps. In your new installation go to settings->plattform->marketplace and search for the AjaxOptOut plugin and install it.

For the integration into the website Matomo is provided as a hugo component. Install it beside your normal theme:

1git submodule add themes/matomo

Afterwards, add your Matomo beside your theme with theme = ["cactus", "matomo"] and to your website template. The footer.html partial is recommended which is located in your theme or your layout folder if you wrote it yourself. The opt-out can be placed anywhere but the privacy page is recommended. I my case the original Google Analytics stuff was placed in the head.html.

1<!-- Includes the tracking code -->
2{{ partial "matomo-tracking" . }}
3<!-- The opt-out shortcode -->
4,{{ matomo-optout >}}

Change the config.toml and add the following lines:

2  url = "https://<your-domain>"
3  id = 1

You can further change the opt-out messages in the config.toml with:

2  button = "Datenerhebung zulassen"
3  message = "Ihre Sitzungsdaten werden erhoben."
5  button = "Datenerhebung abstellen"
6  message = "Ihre Sitzungsdaten werden *nicht* erhoben."

You can configure the Opt-Out style by overriding the provided one with a new asset file assets/css/matomo-optout.css.


We have setup a workflow for a personal webpage and blog driven by emacs. I’ve found the handling fairly easy and hugo as a good tool for creating static websites.

From here on, you can tweak our webiste further by modifying the theme or using modules provided by hugo. For a comment section I can recommend cactus.

So, go further and leave me a comment.