Articles

Blogging using Emacs Org Roam and Hugo

Situation There are not many software preferences in my list that I can not easily replace with alternatives due to how intuitive they feel. Org and Hugo are definitely among the top items of that list. Well, their integration, even though it has existed for a long time, is not the best out of the box. Also with the advancements of cross-article linking, I really wanted to have it in my new blog, which I’m going to use for daily blogging, and also publishing my long form notes and thoughts.

Fixing high resolution font aliasing for Wayland on NixOS

Problem With the progress on Wayland architecture and the usability of its software ecosystem, I’m adapting it more and more. This is quite easy to achieve these days, specially with NixOS friendliness of Hyprland. However, I had a weird issue with my Framework 13th’s high resolution monitor. The font’s on Chrome based applications were looking pixelated, and specially in high res, it wasn’t just the look of it which wasn’t appealing, but I was losing the readability.

Download Sharepoint and Teams videos

Problem I recently needed to download a Teams meeting’s recording from Sharepoint within my organization, and it wasn’t possible due to my access level. I knew from a while back, that there was an all in one solution called Sharedown which uses Puppeteer to trigger a browser in order to download the media from a Microsoft Team account. However, the related package on Nixpkgs for some reason couldn’t be installed on my system, and I didn’t have time to dig into it.

Blogging using Emacs Org Roam and Hugo

Planted February 26, 2025
Categories: Quick Tips
Tags: emacs , org-mode , org-roam , hugo

Situation

There are not many software preferences in my list that I can not easily replace with alternatives due to how intuitive they feel. Org and Hugo are definitely among the top items of that list.

Well, their integration, even though it has existed for a long time, is not the best out of the box. Also with the advancements of cross-article linking, I really wanted to have it in my new blog, which I’m going to use for daily blogging, and also publishing my long form notes and thoughts.

Also knowing myself, I wanted to have the most straightforward setup, with as few external package as possible. So, here is how I do it:

My Setup

Dependencies

To install dependencies, I use devenv like any other project I have. So I don’t need to worry about forgetting stuff in the long run.

First and foremost, I use it to install hugo:

packages = with pkgs; [
  hugo
]

But also to install modern web dev utilities for the blog template I use, so:

let
  nodejs-packages = with pkgs.nodePackages; [
    vscode-langservers-extracted
    postcss-cli
  ]
in {
  name = ...;

  ...
  packages with pkgs; [
    ...
  ] ++ nodejs-packages;

  languages.javascript.enable = true;
}

Also to make it convenient (usually for when I’m updating something in the theme), I add the following process:

processes.hugo-watch.exec = "hugo server -D";

So I can devenv up and forget!

Org Roam

The next thing of course is to set up org roam. There are many different custom setups one can apply (duh, it’s Emacs after all). And that doesn’t much matter here. What matters is how we use Org Roam for this blog. So I create a .dir-locals.el to configure it specifically for the given directory, and this is what I have in it:

((org-mode . (
         (eval . (let ((project-root (locate-dominating-file default-directory ".dir-locals.el")))
                   ;; Set org-roam-directory to the 'roam' subdirectory of project-root
                   (setq-local org-roam-directory (expand-file-name "roam" project-root))
                   ;; Set org-roam-db-location inside org-roam-directory
                   (setq-local org-roam-db-location (expand-file-name "org-roam.db" org-roam-directory))
                   ;; Set org-hugo-base-dir to the 'content' sibling directory
                   ;; Re-initialize org-roam to pick up new settings
                   (when (fboundp 'org-roam-db-autosync-enable)
                     (org-roam-db-autosync-enable))
                   ;; Automatically invoke ox-hugo
                   (org-hugo-auto-export-mode)
                   )))
      ))

I tried to describe it in the comments, but in general what it does, is to make sure it won’t effect your main setup, and opens a new note vault (is that what it is called?) in the given path.

Consequently, I can write my articles or notes using Org Roam, and have the cross links out of the box. Now I need to help Hugo understand it:

Org Hugo

A few more lines in the .dir-locals.el, and that’s also taken care of:

((org-mode . (
         (eval . (let ((project-root (locate-dominating-file default-directory ".dir-locals.el")))
                   ;; Set org-roam-directory to the 'roam' subdirectory of project-root
                   (setq-local org-roam-directory (expand-file-name "roam" project-root))
                   ;; Set org-roam-db-location inside org-roam-directory
                   (setq-local org-roam-db-location (expand-file-name "org-roam.db" org-roam-directory))
                   ;; Set org-hugo-base-dir to the 'content' sibling directory
                   (setq-local org-hugo-base-dir (expand-file-name "." project-root))
                   ;; Do not nest them, I use tags to identify blog
                   ;; posts. I tried using sub directories, and it
                   ;; started to fail on finding links
                   (setq-local org-hugo-section "")
                   ;; Ensure ox-hugo exports yaml FrontMatter
                   (setq-local org-hugo-front-matter-format "yaml")
                   ;; Re-initialize org-roam to pick up new settings
                   (when (fboundp 'org-roam-db-autosync-enable)
                     (org-roam-db-autosync-enable))
                   ;; Automatically invoke ox-hugo
                   (org-hugo-auto-export-mode)
                   )))
      ))

This package is an awesome way to translate your org vault into a Hugo friendly output. This config literally says, get every org file edited in this directory, and export it after saving to the content folder of the Hugo.

However, I need a bit more control, in article level, where I want to put them into different folders according to their purpose. For that I use the following tag:

+#hugo_section: articles

And that’s it.

Org Download

Sometimes, I also need screenshots, and this package is what I use globally with my Emacss configuration.

(leaf org-download
  :url "https://github.com/abo-abo/org-download"
  :doc "Drag and drop images to Emacs org-mode"
  :ensure t
  :after org
  :custom
  (org-download-method . 'direcory)
  (org-download-heading-lvl . nil)
  (org-download-timestamp . "_%Y%m%d-%H%M%S")
  (org-image-actual-width . t)
  (org-download-screenshot-method . "grim -g \"$(slurp)\" %s")
  :config
  (customize-set-variable 'org-download-image-dir "images")

  (require 'org-download))

And then org-download-screenshot whenever I need it.

Serving

To serve, I push everything to GitHub. And from there, Cloudflare serves it over a worker.

How do I like it?

Well, overall I like the simplicity of the approach. There are sometimes some hoops I need to jump to get what I need, but I assume I’ll figure them out gradually and fix it. Just like any other aspect of this project.

Feedback