Yet Another Site Revamp (devopsdirective․com v3.0)
At the time of publishing this article (2025-07-09), I hadn't published an article to this site in 602 days! 😱
I'm not 100% sure why this is, but I think it as at least partially due to the friction involved with creating and publishing posts.
Previous Setup
For a full history of the tooling for this site see:
- The Making of This Site (Hugo, Caddy, + GCP)
- CI/CD for this site (Hugo + Cloud Build)
- Static Site Hosting Using Google Cloud Storage and Cloudflare (with SSL!)
- Blog Workflow Improvements
Previously, the site was built using https://gohugo.io/. I authored posts in VS Code, either locally or using GitPod, and committed the changes to GitHub, where a GitHub Action handled building/pushing to Google Cloud Storage to be served.
This worked fine, but didn't ✨spark joy✨
Despite my previous attempts to improve the workflow, the (small) friction of authoring in VS Code and interacting with Git directly were still annoying. I also had a couple of custom Hugo components for links and images which added additional micro-cuts to the workflow.
Should I use Ghost?
As I considered what to do, I once again revisited https://ghost.org/ as a potential option. It is slick, but requires either:
-
Paying them to host it (which gets quite pricey as the number of members grows)
-
Hosting it yourself and managing:
- The Ghost application
- MySQL database (+ backups)
- A Mailgun subscription
- A Cloudinary subscription
I played around with hosting it on Railway with this template, but ran into some wonkiness around:
- Healthchecks: I was unable to get Railway's healthchecks for the Ghost application working properly on Railway (which in turn prevents zero downtime deploys) for two reasons:
- https redirect: Railway healthchecks are made without TLS and ghost returns a
301
redirect fromhttp -> https
. I think this could be worked around by setting this http header:X-Forwarded-Proto: https
(but Railway does not allow setting custom headers for healthchecks). - hostname: Railway healthchecks originate from
healthcheck.railway.app
. Again, if we could set custom http headers, we could use:Host: devopsdirective.com
, but we can’t.
- https redirect: Railway healthchecks are made without TLS and ghost returns a
- Volume Snapshots: Volume snapshots on Railway are only available to pro users. I could have implemented a separate mechanism to backup the database OR committed to this costing at least $20/month... but I'm irrationally frugal (sometimes to my own detriment)! 🤑
That canvas view is 🔥 though!
Asking for Inputs
Two days ago I decided to see if anyone had recommendations for a blog setup they loved, and Victor Buendia recommended using Obsidian!
He might be onto something 👀
I already use Obsidian for note taking, so the questions for me were:
- Can I make it do what I want it to?
- How seamless the publishing could be?
Obsidian Digital Garden
Turns out, a Norwegian legend named Ole Eskild Steensen has already done all of the hard work to make managing and publishing a website from Obsidian super nice with his project:
How Does it Work?
The setup instructions can be found here: https://dg-docs.ole.dev/getting-started/01-getting-started/, but essentially you:
- Install the Digital Garden obsidian plugin
- Create a GitHub repo to store the site contents
- Authenticate the plugin via a GitHub personal access token
- Configure Vercel to build/deploy the site
- Add
dg-publish: true
to the frontmatter of any Obsidian note you want to publish - Use the Obsidian command pallet to publish one (or all) specified notes
The magic happens with:
- The Plugin:
- Determining which notes + images need to be published
- Generating the appropriate file structure expected by the Eleventy configuration
- Automatically handling the git operations to commit and push those
- Vercel:
- Building each commit with Eleventy
- Deploying each commit
My New Workflow
I have a dedicated Obsidian vault for this website. It is synced across computers and mobile via iCloud. The daily note is configured to use the following template:
---
dg-publish: false
dg-pinned: false
dg-hide: false
tags:
dg-published-at: {{date}}
title: foo bar baz
---
and is created at: /Posts/YYYY/MM/DD
.
So, to create a post:
- Use the Obsidian "Daily Notes: Open today's daily note" command
- Update the note name + title
- Write the actual post 😅
- Toggle
dg-publish: true
- Use the Obsidian "Digital Garden: Publish Multiple Notes" command
...about 30 seconds later the post is live!
For images, I paste them into obsidian directly. The initially are stored in /attachments
with an autogenerated name like Pasted Image 20250709113743_986
, but I usually move them to a directory alongside the post note and rename to something meaningful.
Porting Everything Over
In order to make the switch, I needed the corpus of articles already published migrated over.
Luckily, Hugo mostly uses vanilla markdown for authoring posts, so I was able to copy everything over and make a few minor tweaks to:
- Images: I had custom Hugo components for handling images (and images that are also links).
I updated these to be:{{< img-link "path-to-image.png" "https://some.link" "Some caption" >}}
[![[path-to-image.png]]](https://some.link) *Some caption*
- Links: In Hugo, default markdown links opened within the same tab so I had a custom link component to open links in new tabs.
I updated these to be:{{< link "https://some.link" "Displayed text" >}}
%% External Links %% [Displayed text](https://some.link) %% Internal Links %% [some-internal-page|Displayed text]
I could have scripted this, but the number of pages was such that I just converted these manually. Let me know if you notice an image or link that seems broken! 🙏
Javascript sorting woes
Everything was looking great, when I realized that the sorting of posts was wonky.
10, 11, 02, 03,... WTH?
When constructing my directories (and permalinks), I had used left padded zeros to ensure that the months would sort correctly numerically AND alphabetically.
So what is going on here? The digital garden site has a method which sorts these recursively which can be found here: filetreeUtils.js
This CORRECTLY sorts the months as you would expect. However, this sorted tree is stored as a normal Javascript object which gets rendered by a Nunjucks template here: filetree.njk
This traverses the keys of the Javascript object, resulting in:
"10"
,"11"
, and"12"
being interpreted as integer indices"01"
,"02"
,"03"
, ...etc being interpreted as non-integer indices
Given that, the ES6 Rules specify:
- First, the keys that are integer indices, in ascending numeric order.
- Then, all other string keys, in the order in which they were added to the object.
- Lastly, all symbol keys, in the order in which they were added to the object.
(See https://2ality.com/2015/10/property-traversal-order-es6.html for more detail on what "Integer Indices" actually are)
We end up with this incorrect ordering! 🤬
My solution was to strip the leading zeros when constructing the key:
folders = note.filePathStem
.split("notes/")[1]
.split("/")
// Here we strip leading zeros on the month folder to avoid JS treating 10, 11, 12 as integers,
// but 01, 02, 03 as strings and sorting incorrectly when displaying with nunjucks. This allows us to
// keep the leading zeros so that posts from our old hugo site all have the same URL structure
.map((str) => str.replace(/^0+(?=\d)/, ''));
}
This gives me the sorting I want, while maintaining the URL structure from my old site (with leading zeros) to avoid breaking old links.
Isn't Vercel overengineered for a static site?
The site is built with Eleventy, which generates a static site that could be hosted in many ways (including the old GCS + Cloudflare setup I had before). I could totally update my old GitHub action to use Eleventy (instead of Hugo) to build the new site and deploy to GCS as I was before (and I may do that some day).
For now, I used Vercel because the Obsidian Digital Garden project has a "Deploy to Vercel" button which creates the repo and wires up the necessary configurations. This enabled me to get up and running in just a few minutes! I will monitor if anything ends up costing me (e.g. Vercel's Image optimization or total volume of traffic served), but for now I'm happy with the setup.
Speedrun a New Post
Okay, let's now test how long it takes me to create a new post.
34 seconds!
This is a test to see how fast I can create a post
Will this revamp actually inspire me to write more often? I'm hopeful, but only time will tell... 🤞