Blogging with Emacs, and Emacs only
Published on November 1, 2018

Table of Contents

Some time ago I wrote about my blogging workflow: I basically used the almighty org-mode in Emacs to write the posts, and afterwards let hugo generate the static website. hugo is a great tool, and the results are generally good. However, hugo and I seem to make different assumptions on how org should be written. I am one of those 79-characters-wide-text hooligans, so my org code is hard-wrapped. This seems to produce all kinds of export errors in lists, footnotes, and other artifacts that seem to not expect a line break 1. Furthermore, I am not that savvy in Go, so I cannot really hack with it and modify it.

On the other hand, org was always on point exporting how things are done: no errors, ever, and as easy as 4 keystrokes when I am in a buffer. I used it for my work planning, for my agenda, my notes, my assignments… I knew how to tinker with it properly, but I only used it for Latex export. A few weeks ago, I decided to export a file to HTML, and I saw pretty much understood what was going on: what elements in org were mapped to in HTML, what elements could be enabled and disabled… And I also noticed that small section in the export menu: "publish". After reading a bit about it, I decided to take the shot: I was going to migrate my blogging workflow to org-publish.

Publish configuration

org-publish configuration

org-publish is based in projects: projects are policies defined for a set of files on how they should be exported when that project is executed. You can define as many projects as you wish, even composing them! And that is exactly what my current setup is: I have a project to export posts and other org content ("blog-notes"), a project for static files that should not be manipulated ("blog-static") and a parent project that ensures that both of them are executed ("blog"). Therefore, we just need to set the list org-publish-alist to include the projects. These are defined using property lists where we set all the important variables for each of them:

  • base-directory is the folder where your org files (or other static content) will be hosted. Think of it as your actual workspace.
  • publishing-directory is the folder where the exported files will be generated to. This directory replicates base-directory's structure. If we set things properly, you should never need to actually touch these files directly!
  • base-extension is a regex that will match against all the files that you want to be associated with this project.
  • publishing-function is the function that will be used to export the files contained in a project.
  • html-preamble and html-postamble are strings that will be added before and after the exported body in HTML. I use these variables to include a header and a footer for the website.
  • components can be used to declare a list of projects to be run together.

There are a lot of variables that you can set, and you can check out a list of them in the org documentation. Here you can see as a example the code for the blog posts and other org content:

(setq org-publish-project-alist
      '(("blog-notes"
         :base-directory "~/Projects/blog/org"
         :base-extension "org"
         :publishing-directory "~/Projects/blog/public"
         :recursive t
         :publishing-function org-html-publish-to-html
         :headline-levels 4
         :section-numbers nil
         :html-head nil
         :html-head-include-default-style nil
         :html-head-include-scripts nil
         :html-preamble my-blog-header
         :html-postamble my-blog-footer)

        ;; Define any other projects here...
        ))

This is the one with all the important settings, but I have defined two more projects: one that looks for all the static files like images that I want to publish as well (which simply copies them from the org/ folder to the public/ repository) and another one that executes both of the aforementioned projects at the same time (to build the complete blog). If you would like to check out the rest of the code, feel free to browse the appropriate section of my Emacs configuration.

Styles using CSS

Next step is to design a proper style sheet using CSS. For this task I decided to approach a simple style, trying to favour readability and faster loading times. Also, I am really not a fan of animations in most websites, so I prefer to discard most (if not all) JavaScript code from the plans. The exported HTML of org-mode has some quirks and nuances to take into account, and I decided to start from the already great CSS provided in Solarized CSS (parts of which are still in my very own style sheet). After a couple iterations in the design, I finally found a great and simple style to match, based on readable fonts and easy to read contrasts.

This style is licensed under MIT and available in the repository org-css. Although it is written for the blog, I also tend to use it when I export some document to HTML (it works beautifully every time!).

Short and clean links: my pet peeve

One of the things I struggled most was actually one of the less important ones. For different situations, I think that having "pretty" links is actually really important (I know this can be strange at first, just bear with me). With this I mean that I want this post to be diego.codes/post/blogging-with-org/ rather than diego.codes/post/blogging-with-org.html (notice the ending). In my humble opinion this seems not only cleaner at first sight, but also slightly more practical if I have to point to a certain address talking rather than linking (imagine a project, a talk, of other kind of resource like it). Also, this gave me a great excuse to hack with org-mode in a whole new level: export filters.

This filters are functions that can be defined to manipulate the output of org-export auto-magically. In this case, we will define a function to be added to the link filters: each time org stumbles upon a link, the function will be called with the resulting output of the link, the backend being used as a symbol (Latex, HTML, etc…) and one more list containing different channel info. Therefore, this is exactly what I need to filter the links! Assuming that this is the scheme of my post directory:

~/Projects/blog/org $ tree post/
post/
├── first-post
│   └── index.org
├── [...]
└── som-tsp
    ├── index.org
    ├── italy.png
    ├── uruguay.gif
    └── uruguay.png

Each post being contained in the index.org file (that will be exported to index.html), the routing is something that is already working. Cleaning the links can be done using some trivial Emacs Lisp code:

(defun filter-local-links (link backend info)
  "Filter that converts all the /index.html links to /"
  (if (org-export-derived-backend-p backend 'html)
      (replace-regexp-in-string "/index.html" "/" link)))

;; Do not forget to add the function to the list!
(add-to-list 'org-export-filter-link-functions 'filter-local-links)

And that's it! All index.html links will now point to its directory, and we will let the browser find the HTML file by itself. These filters are super powerful and, as you can guess, can be used with whichever backend you are using to export.

… And privacy for all

Even before migrating, I noticed some things that made even more self conscious of how small was the control I had over my blog: as someone that is actively dedicated on pursuing privacy, I realized I was doing a terrible job for my readers. Even though my blog was a static site, it had several trackers included that were related to different services I used. While migrating I realized that was even worse: some of the third party petitions where completely unnecessary and if someone was blocking from loading them (which is an entirely reasonable thing to do in a blog) it was probably finding some breaking errors. I just intended to share some of the things I learned, so I found most of these services not needed or easy enough to substitute. Here is the full list of sacrifices and improved services in this migration.

Using Google Analytics was simply a not well-thought decision. My hugo theme offered a simple way to include a Google Analytics token, so I chipped in just to try. What I found was an ubiquitous and thorough analysis of the visitors to my page: the perfect ego-trip tool. I never really though that it would include also giving Google tons of information about my readers in exchange for a few chart pies that are not of much use to me. That's why I decided to simply discard this service: if I need it in the future I will simply look for a better alternative.

The worst decision was to use Disqus. One day, while checking one of my posts on my phone, I discovered that Disqus was not only loading a lot of resources, but also was injecting ads into my posts. It was also miserably failing at its only purpose: centralising the discussion of the post. Instead, the discussion was scattered across Reddit, Lobsters, HackerNews and some other aggregators. Disqus asks to create an account to leave a comment, which is enough to dissuade most users to even try to do it. It is also owned by Zeta Global, a marketing agency that is most likely tracking their users across the internet a la Google. Therefore, I decided to simply not include a comment section any more, and let the readers join the discussion they prefer.

The last step was to get rid of the final tracker: Google Fonts. Although it was the last one, it actually was one of the easiest. Simply by downloading the fonts and uploading them to the repository, and creating all the @font-face tags to be used, it is possible to discard the trackers used by Google because of the typography of your blog (which is, actually, one of the weirdest reasons to include actually two trackers).

Now that there were no more third-party requests, its only left to add a proper HTTPS configuration. Although some people may argue its importance for a simple blog, I like to ensure my readers that they are reading the blog they are intended to. In GitHub Pages, after a four-year-old struggle, it is now possible to automatically generate an HTTPS certificate for a custom domain (plus, it is automatically renewed, which is something I messed up a couple of times when I had a Let's Encrypt / GitLab setup).

The results

I am incredibly happy with the results. It feel super natural for me to integrate my workflow with my current setup and I am also quite pleased with the final result of the blog. I like the simplicity, the usability and how this extra work allowed me to learn a lot about web and how to build an overall nicer alternative for my readers, dropping all bloat and trackers from it. Also, the style of the blog is available for any other documents I want to export from org-mode to HTML, and directly using the built-in exporter allows for all the features being properly exported (including packages, for example, org-ref). The flexibility offered is great, specially if you don't mind (or like me, actually prefer) tweaking elisp rather than any other tool.

Footnotes:

1

As u/kaushalmodi pointed out in r/emacs, the easiest way to solve these problems are using the package ox-hugo to solve this problems while maintaining the speed and taxonomies, as well as adding some flexibility. However, we are not here to talk about the easy mode. Also, my blog is tiny, so speed is not a deal breaker for me.