Writing a blog with Emacs and Hugo
Summary⌗
I decided to start this blog because I’ve accumulated a bunch of notes and documentation from my various personal projects. I regularly take notes to keep track on my projects progress and to have a handy reference for the future. My note-taking tool of choice is Emacs org-mode and I recently came across a convenient method to transform these notes into blog posts using Hugo and ox-hugo.
I am not going to enter into details on how Hugo and ox-hugo work, but in summary, Hugo serves as a framework for generating static websites. To add new content to my website I just need to export my notes to markdown with some metadata headers to indicate blog post date, author, title and other information. ox-hugo will take care of exporting the org notes to markdown and Hugo will take it from there.
Installing Hugo⌗
Hugo has a pretty clear documentation. This is what I did to install Hugo and the current theme of my blog.
cd ~/workspace
hugo new site chelmiki.com
cd chelmiki.com
git init
git submodule add https://github.com/panr/hugo-theme-terminal.git themes/terminal
echo "theme = 'terminal'" >> hugo.toml
hugo server
After running hugo server
we can locally access our newly created website at http://localhost:1313/
Blog structure in org-mode⌗
The most common ways to organize a blog in org-mode are:
- One post per file
- One post per subtree
Both ways are described in the ox-hugo website. I decided to go with the recommended option one post per subtree. All the blog content is in a single org file with the following structure:
Content of /org/chelmiki_com.org
#+HUGO_BASE_DIR: ~/workspace/chelmiki.com/
#+HUGO_SECTION: posts
#+author: Miguel
* Hugo Howto
* Blog Ideas
* TODO New blog post idea
* Emacs
* DONE Writing a blog with Emacs and Hugo :emacs:
* Ham :@ham:
* AWS
* Misc
There are some org headers at the top of the file that tell ox-hugo which metadata add to the markdown files (author) and where to export them (~/workspace/chelmiki.com/content/posts).
Capture templates⌗
I have configured a capture template so I can quickly capture new blog post ideas without disrupting my current work. This capture template allows me to quickly record a blog post title/idea, drop some draft notes if needed and quickly get it out of my way and return to work. The new blog post idea gets recorded and stored in the blog file under the header Blog Ideas in TODO state.
(with-eval-after-load 'org-capture
(defun org-hugo-new-subtree-post-capture-template ()
"Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information."
(let* ((title (read-from-minibuffer "Post Title: ")) ;Prompt to enter the post title
(fname (org-hugo-slug title)))
(mapconcat #'identity
`(
,(concat "* TODO " title)
":PROPERTIES:"
,(concat ":EXPORT_FILE_NAME: " fname)
":END:"
"%?\n") ;Place the cursor here finally
"\n")))
(add-to-list 'org-capture-templates
'("h" ;`org-capture' binding + h
"Hugo post"
entry
;; It is assumed that below file is present in `org-directory'
;; and that it has a "Blog Ideas" heading. It can even be a
;; symlink pointing to the actual location of all-posts.org!
(file+olp "chelmiki_com.org" "Blog Ideas")
(function org-hugo-new-subtree-post-capture-template))))
An example of template can be found here.
Exporting from org-mode to Hugo⌗
When I want to export a finalized blog post or a draft I execute M-x org-hugo-export-wim-to-md
. This will export the blog post at point to the location configured in org file header:
#+HUGO_BASE_DIR: ~/workspace/chelmiki.com/
#+HUGO_SECTION: posts
#+author: Miguel
The new post will be created in ~/workspace/chelmiki.com/content/posts/
Below we can see how the generated markdown file will look like. It starts with some headers that define some of the metadata used by Hugo when creating the final HTML files. The metadata contains the blog post title, the author and the publishing date or whether the blog post is a draft. The headers are followed by the blog post content in markdown format.
+++
title = "Writing a blog with Emacs and Hugo"
author = ["Miguel"]
draft = true
+++
## Summary
Testing your website locally⌗
I am running my web server in a VPS but I like to locally test new changes before publishing them. I run hugo web server locally from the directory created by Hugo for our project:
cd ~/workspace/chelmiki.com/
hugo server
I can visualize a local version of my website at http://localhost:1313/.
If I want to include Draft posts I can add the -D
flag to tell Hugo server to include draft posts:
cd ~/workspace/chelmiki.com/
hugo server -D
I keep the server running while I am writing a post and export and refresh http://localhost:1313/ as many times as necessary.
Web server configuration⌗
For the web server I am using NGINX with the following virtual host configuration which listens for HTTP traffic in ports 80 (HTTP) and 443 (HTTPS) redirecting all HTTP traffic to HTTPS:
server {
listen 0.0.0.0:80;
listen [::]:80;
server_name chelmiki.com;
server_tokens off;
access_log /var/log/nginx/chelmiki.com_access.log;
error_log /var/log/nginx/chelmiki.com_error.log;
index index.html;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/privatekey.pem;
access_log /var/log/nginx/chelmiki.com_access.log;
error_log /var/log/nginx/chelmiki.com_error.log;
server_name chelmiki.com;
root /var/www/chelmiki.com/public/;
index index.html;
}
Exporting to our server⌗
When I finish writing a blog post and it is ready to be published I change the status to DONE. That would remove the draft flag when exporting and set the publishing date. Right after that I use rsync to synchronize my local project with my VPS.
cd ~/workspace/chelmiki.com/
hugo && rsync -avz --delete public/ chelmiki.com:/var/www/chelmiki.com/public/
The workflow⌗
This is a description of the whole workflow, since capturing the initial idea until publishing it.
- Whenever I have a new idea for a blog post I use the capture template to quickly record the blog post title and take some brief notes if needed and immediately get back to my current work.
- When I feel like I want to write the content of the blog post I will open my blog org file and navigate to the
Blog Ideas
header. There I will find the new blog post that was created in TODO state and I can now write the content. At this point I can also decide to re-file the blog post fromBlog Ideas
to its final categoryEmacs
. I can also apply org-mode tags to my blog post which will turn into tags in my blog. - I will export the new post
M-x org-hugo-export-wim-to-md
and run the local Hugo serverrun hugo server -D
to be able to see the new blog post at http://localhost:1313/. I can keep editing and exporting the content multiple times until I am happy with the results. - Once the blog post is ready I will switch its status to DONE to remove the Draft mark and set the publishing date.
- Finally I will synchronize the content of my blog with my VPS what will effectively publish the new blog post.