This article shows how to create a website with Emacs Org mode and Hugo. Create a beautiful and functional static website with the Emacs text editor.

Create Websites with Emacs: Blogging with Org mode and Hugo

Peter Prevos

Peter Prevos |

2324 words | 11 minutes

Share this content

When I developed my first website in 1997, I wrote HTML in a plain text editor. When discovering WordPress when it was first released, I thought it was the best thing since sliced bread because it allowed me to create websites without managing individual HTML and CSS pages.

Unfortunately, WordPress has become bloated, and the online editor is inconvenient. Last year, I bid farewell to WordPress and now use Emacs Org mode and Hugo to develop websites.

A significant disadvantage of WordPress is that you write your text in the browser, and your content resides inside a database. You can, as such, only use WordPress to work on your website. The advantage of using Emacs and Org mode to maintain a website is that your content consists of plain text files. There are no databases or access limitations, and you get to use the incredible editing powers of Emacs.

Another reason to ditch WordPress is that static websites are faster and safer because there is no database to hack. A static website generator creates pages in plain HTML, which render faster than a database-driven website, improving the user experience and your SEO. Static websites have at least three advantages over database-driven systems, such as WordPress:

  • Faster website due to static content (no need to access a database)
  • Writing web pages offline with your favourite editor
  • Your content remains in plain text
  • No dependence on third-party paid plugins

This website is entirely created with Emacs Org mode and Hugo, and this post explains how you can do the same.

This article is part of the Emacs Writing Studio, which explains how to use Emacs to undertake research and write and publish articles, books, and websites. You can find the most recent configuration files on GitHub:

Org mode

Org mode is a markdown language in plain text. This approach helps you to focus more on your writing instead of its design because it emphasises content over formatting. Text editors are not like What-You-See-Is-What-You-Get systems. You write the article in plain text, and when you publish the website, the content is merged with a style sheet that determines the design. The image below shows this article as I write it and what it looks like in the browser.

The WYSIWYM approach with Emacs Org Mode and Hugo.
The WYSIWYM approach: Left, an article in Emacs. Right: The exported result.

Emacs can create simple websites with the default Org mode export function. While this function has a powerful option to use CSS templates, it is not a fully-fledged static website generator. That is why I use Hugo to generate HTML files.

Hugo static website generator

The Hugo static website generator converts a Markdown or Org mode file collection to a website using the Go language. You can use Hugo with Emacs out-of-the-box. The go-org library by Niklas Fasching forms part of Hugo, so in principle you do not need to update your init file.

You will need to install the Hugo software, create an empty site and download a theme before starting. The Quick Start guide for Hugo provides detailed instructions. When you get to step four, come back to this article to find out how to use Org Mode to add some content.

The video series by Mick Dane embedded below provides a comprehensive introduction to using Hugo.

Introduction to Hugo by Mick Dane.

All you need to do now is start writing pages in Org mode or Markdown in the content folder. Emacs can also help you with Markdown, but that is outside the scope of this article.

The theme for this website is optimised for working directly with Org mode files in Hugo. You can download the theme from GitHub. The Hugo website provides access to an extensive collection of themes. Changing the look and feel of your website is as easy as changing the theme.

Using Org Mode and Hugo

The Hugo website does not provide much information about using Org mode. The Go-Org website has a detailed overview of Org mode syntax and how Hugo renders the HTML.

The Org mode module in Hugo perfectly parses your org files, but there are some special considerations and limitations.

 

Front matter

Each Org mode file starts with the front matter containing your blog post's title, author, date, and other metadata. You can add non-standard variables, which you can use in your Hugo shortcodes or partials, for example:

  #+Title:        Create Websites with Emacs: Blogging with Org mode and Hugo
  #+date:         [2021-05-07 Fri]
  #+lastmod:      [2023-09-09 Sat]
  #+categories[]: Productivity
  #+tags[]:       Emacs Hugo
  #+images[]:     /images/emacs/emacs-hugo.jpg
  #+weight:       62
  #+example:      Hello world!

Any line that does not start with #+ ends the front matter.

Keyword values can be either strings (#+key: value), or a whitespace-separated list of strings (#+key[]: value_1 value_2). If you need to add a value with two words, you will need to use a hyphen.

Add single keywords to your template with, for example with {{- .Params.example =-}}, which renders as "Hello world!"

When working with a list, spaces act as a delimiter, so you cannot use tags with two words without a workaround, for example hyphenation ("Hello-World").

If you are handy with Hugo templates, then you can easily remove the hyphens. Modify your Hugo template files that call the relevant list variables to replace any hyphens with spaces:

{{- range .Params.tags }}
{{ replace . "-" " " }}
{{- end }}

You can instruct Emacs to automatically add a modification timestamp by adding the following code to your init file:

  ;; Update Org files with last modified date when #+lastmod:
  (setq time-stamp-active t
        time-stamp-start "#\\+lastmod:[ \t]*"
        time-stamp-end "$"
        time-stamp-format "[%04Y-%02m-%02d %a]")
  (add-hook 'before-save-hook 'time-stamp nil)

This hook will look for #+lastmod: in your file and add the current date. Hugo will recognise the lastmod variable, and if your template supports it, it will insert this date onto your page.

 

Internal Links

Org mode includes the ability to link to other pages. Unfortunately, you cannot use standard Org mode or Denote links in Hugo for two reasons.

Firstly, in Hugo, the slug of a post is not necessarily the same as its relative filename. When using the #+slug: variable in the front matter, the URL will not match the filename.

Secondly, the file's location on your drive might be in a different folder than it appears on the website.

Hugo uses the ref shortcode to manage these issues. This shortcode inserts the URL of the post into the link. When you move a post to another folder, Hugo finds it and creates the appropriate hyperlink. Hugo will throw an error when it cannot find your file during rendering.

You can create links between your pages using standard org mode syntax and the ref Hugo shortcode, which looks like this:

{{< ref filename.org >}}

Use the C-c C-l shortcut (org-insert-link) and enter the shortcode and a description. The source code of the link will look like this:

[[{{< ref filename.org >}}][Description]]

To find the filename of the post you want to link to, open the Dired file manager with C-x d and find your post. Press w to copy the filename and paste it into the link.

When the target is an index page, then you need to add the sub-folders starting with a forward slash and remove the _index.org filename. Please note that paths with a non-alphanumeric symbol, such as a directory separator /, need to be quoted.

[[{{< ref "/tags/emacs" >}}][Index Page]]

Org mode has flexible options to create special types of links. We can leverage this functionality to simplify creating internal Hugo links. Creating a new Org mode link type with some Lisp makes life slightly easier. Add the code below to your Emacs configuration.

This capability consists of three bespoke functions. The ews-get-hugo-directory and ews-hugo-list-content functions check whether the current open buffer is part of a Hugo project by locating the configuration file. If this is the case, then it presents a list of all articles and pages in your website. The ews-hugo-link-complete function lets you select a page or post of your website to link to. This function also manages the specific requirements for index pages.

Press C-l to create a link, and type hugo: and enter. Pick a file, hit enter, provide a label and hit enter again.

  ;; Create Hugo links
  (defun ews-get-hugo-directory ()
    "Lists the directory of the current Hugo website or nil."
    (when (string-match "\\(.*\\)content" default-directory)
      (match-string 1 default-directory)))

  (defun ews-hugo-list-content ()
    "List the content of the Hugo website of the current buffer.
  Return an error message when not in an apparent Hugo directory."
    (if-let* ((hugo-dir (ews-get-hugo-directory))
              (hugo-p (directory-files hugo-dir nil "^config\\..*"))
              (content-dir (concat hugo-dir "content/")))
        (let ((org-files (directory-files-recursively content-dir "\\.org\\'"))
              (md-files (directory-files-recursively content-dir "\\.md\\'")))
          (append org-files md-files))
      (user-error "Not in a Hugo buffer")))

  (defun ews-hugo-link-complete ()
    "Complete a Hugo weblink through the `org-insert-link' and hugo: hyperlink type."
    (let* ((posts (ews-hugo-list-content))
           (titles (mapcar (lambda (post)
                             (string-remove-prefix
                              (concat (ews-get-hugo-directory)
                                      "content/") post)) posts))
           (selection (completing-read "Choose page:" titles))
           (target (concat "/"
                           (replace-regexp-in-string
                            "_index.*" "" selection))))
      (when titles
        (concat "{{< ref \"" target "\" >}}"))))

  ;; New link type for Org-Hugo internal links
  (org-link-set-parameters
   "hugo"
   :complete #'ews-hugo-link-complete)

This configuration only defines how to create a link. Unfortunately, this approach does not allow you to follow these links as Org mode can only recognise links that start with a string and a colon. e.g. hugo:, while Hugo links start with curly braces.

 

Images

The standard image syntax in Org mode works fine, but you must link the image to its published destination, not the current one. Hugo keeps images in the static folder, so all your image paths assume this folder as the root. Hugo will convert these links to the full URL.

Unfortunately, you cannot preview images in Org mode because you are linking to the website folder, not the Org mode folder. An image stored in <hugo-directory>/static/images/picture.emacs.png will be listed as /images/picture.emacs.png.

Hugo will parse the usual metadata, such as captions, alternative text, title and width. The example below shows how to style an image with alt and title tags and set its width.

  #+attr_html: alt: Alternative text title: Image Title :width 800
  #+caption: Caption text.
  [[/images/file-path.png]]
 

Items and drawers

When writing in Org mode it can be useful to track progress and add notes inside drawers. The standard Org mode export function can be configured to ignore TODO tags and drawers in the export. Because working with Hugo bypasses the Org Mode export functionality, TODO tags will appear in your HTML files. Hugo ignores property drawers but includes the content of any other types of drawers.

 

Limitations

Using Hugo directly with Org mode has some limitations, mentioned above. The main limitation is that it bypasses the Org mode export engine which means that some functionality is unavailable. Org mode macros cannot be used, but you can Hugo shortcodes to insert parametrised bits of text or HTML in your website. For example: {{< youtube w7Ft2ymGmfc >}} embeds a YouTube video with the relevant ID in your page.

Bypassing the export engine also means that any Org mode export variables are ignored, for example to exclude TODO tags or drawers from your website.

To insert images you either use the figure shortcode or the method shown above. Unfortunately you cannot preview these images as you need to enter the image location on your website and not on your computer.

Lastly, links need to use the internal linking shortcode. The configuration shown above helps you insert links, but you cannot follow them.

These limitations have not stopped me to use Hugo and Org mode as described in this article to develop this and other websites.

 

Using ox-hugo

The ox-hugo package in Emacs can act as an intermediary between Org mode and Hugo. This package converts the Org files in your content folder into Markdown files, which Hugo will render.

This package has some advantages over using the plain Org mode method because it relies on the standard Org mode export functionality.

However, I don’t use ox-hugo because I like the flexibility of the plain Org Mode approach. The minor limitations of the native Org mode rendering in Hugo have not prevented me from maintaining my websites. But Emacs is a matter of choice, so feel free to use ox-hugo.

Deploying your website

After adding some content, you can continue following the Hugo Quick Start Guide instructions to run the internal webserver or generate the entire website.

You don’t need to leave Emacs to run these console commands. When you execute the shell function (M-x shell <enter>), you can run Hugo from within Emacs. You will need to execute these files from within the folder that contains your website.

hugo server -D # Run internal web server, showing draft pages

All buffers in the content folder must be saved to disk; otherwise, Hugo will throw an error.

When using the server option in the Hugo command line, the website is available in your local browser under https://localhost:1313.

Exporting your WordPress website to Org mode

Moving a WordPress website to Hugo can be an enormous task that can be semi-automated. You can export the content from WordPress to a CSV file to convert the content to either Org mode or Markdown. I have written some scripts to export my former WordPress websites.

Emacs Writing Studio

Emacs Writing Studio consists of a series articles and a configuration to help you publish articles, books, movie and theatre scripts and websites. You can find the most recent configuration files on GitHub:

Emacs is a malleable system, so everybody will have their personal preferences of how to undertake a task. Any article on how to be productive with Emacs is thus opinionated. If you have a different way of doing things, please share your views and leave a comment below, or complete the contact form to send me an email.

The next article in this series discusses how to manage your projects and get things done with Org mode.

Share this content

You might also enjoy reading these articles

Export WordPress to Hugo Markdown or Org Mode with R

Writing Prose with Emacs

Exploring Your Ideas With the Denote-Explore Package