A few years ago I set up my blog with NGINX, self-hosted on Proxmox. Back then, it felt simple: write notes on my desktop, run the static site generator, and rsync the HTML to my server.
Perhaps the biggest issue I found, however, was the friction between writing and publishing. I write everything in Obsidian - every note, every idea, every post, every reminder. As an additional quirk, I do not keep my vault manicured like all those Youtubers who have extremely fancy and elaborate digital gardens: I use a flat-layout where every entry is chucked into a single folder.
Although it may not seem like much, the extra step between writing and publishing meant I didn’t publish very much. Between version updates, broken networks (mostly my own doing!), and trying to remember how the entire system worked, my enthusiasm for putting stuff on my blog waned quickly. The ideal workflow for all of this is one where I don’t have to leave Obsidian, and, additionally, I don’t need a lot of personal infrastructure to use. This last point became particularly evident when I moved away from Canada, since I lost access to my homelab for many months while my devices were in transit, and I relied on either my phone or my laptop for working and writing.
Aside
The specific layout of my Obsidian vault is the following. As I mentioned before, all my notes go into a single directory,
vault._assetsis used to keep attachments and images to avoid polluting my Markdown files, while_inboxis the place where every new note appears - once I’m done with it, it moves intovault.
highlightsis a directory with book/article highlights pulled from my Kindle, from Readwise, or from any other service for highlight importing. It is not, in other words, my own writing.
glossaryis a set of entries that don’t quite fit a “note”, since they are used primarily to explain terms. They are primarily a product of my time as a 737 pilot where I had to keep track of very many three- and four-letter acronyms (although I intend to expand this glossary into other fields).Finally, there is
MOCs. Maps of Content are simply entry points into my vault - some of them generated with dataview and others handcrafted, but in all cases they operate primarily on my actual note metadata - I could lose the MOCs and at worst it would be a minor inconvenience.All of my notes are synced across my devices using Syncthing
The dependency on my own infrastructure is something that has been very present in my mind lately. I like to self-host as much as I can, but there are times when this isn’t possible - such as these past few months when I’ve been living in a more transitory state, without a specific home base (and much less a place to install my servers). At the same time, I have been thinking a lot about dependency inversion in programming, where the idea is to make sure that an abstraction isn’t dependent on specific details - and thus to allow things to change without having to modify the entire program.
My blog, in its previous iteration, had a massive dependency issue. Writing and publishing were tightly coupled to my home services, to the specific workflows I set-up on a specific computer, which itself was highly reliant on one of my servers. It meant that any change required re-structuring the entire workflow.
Basic requirements
Because writing is thinking, and publishing (even if just for myself) enforces some measure of clarity, I wanted to smooth the process to focus on what matters: publishing my writing, instead of fighting with my code and infrastructure.
The requirements for this blog iteration are simple: I must be able to write everything in Obsidian, I must use a statically generated site, I must not depend on things I have to touch and fiddle with constantly to maintain it, and I must be able to publish from any of my devices, on any network.
For this, Quartz fulfills the first two requirements: it is designed from the beginning to work with Markdown files, and specifically with Obsidian. Using it means I don’t have to bother with figuring out Obsidian-flavoured Markdown when generating the HTML files, I don’t have to worry a lot about the presentation of the site (since it will generally be very Obsidian-esque), I can safely use links and back-links, and I can focus more on writing and less on designing.
With writing covered, I had to figure out how to publish without fiddling. I don’t want to publish all the files I have in my vault, and I also don’t want to keep a separate vault for the blog (since connections between notes are first class). This means being able to maintain the bulk of my vault private. This is combined with the requirement to access all my notes from any device - fortunately, in addition to Syncthing for device-wide access, I also keep my notes in a private git repository.
The build and publish workflow
The current solution uses a few moving parts, but they are all quite straightforward, and none of them force me to change how I write or store my notes.
Because I already store my notes in a private Github repository, I decided to use Github Actions to build and publish the blog; however, I did not want to contaminate my vault with the Quartz configuration and required files. The solution was to create a separate git repo that is a fork of Quartz, as per the documentation.
The next step was to build the site whenever I push a note to my vault repo. This is accomplished with a simple Github Action workflow:
name: Trigger Quartz Build
on:
push:
branches: [master]
workflow_dispatch:
jobs:
trigger-build:
runs-on: ubuntu-latest
steps:
- name: Trigger Quartz repository build
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.QUARTZ_REPO_TOKEN }}
repository: adnanvaldes/arvb
event-type: vault-updated
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'This simple file simply notifies my public Quartz repo that an update has been pushed. The Quartz repo, then, uses the following action:
name: Deploy to Cloudflare Pages
on:
push:
branches: [v4]
repository_dispatch:
types: [vault-updated]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-22.04
steps:
- name: Checkout Quartz repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Quartz dependencies
run: npm ci
- name: Checkout vault repository
uses: actions/checkout@v4
with:
repository: adnanvaldes/vault
path: ./vault
token: ${{ secrets.VAULT_ACCESS_TOKEN }}
- name: Build site with Quartz
run: npx quartz build --directory=./vault
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: arvb
directory: public
gitHubToken: ${{ secrets.GITHUB_TOKEN }}While slightly more complex, the steps are fairly simple:
- Checkout my vault repository
- Install Node and Quartz
- Build the HTML files
- Push them to Cloudflare Pages
In short, with these two actions I can write and push my notes to their own repo and simply not think about building and monitoring. Whatever gets pushed to my vault, from any device, triggers a build and deployment, without forcing my vault to be dependant (or in fact to even know about) Cloudflare Pages, repositories, or actions (beyond the simply trigger action - the first iteration of this completely decoupled the repositories by having an hourly update, but I like being able to see the updated site whenever I finish writing a new post).
With this setup, I no longer think about how to publish; I just write, push, and watch my blog update automatically. The infrastructure is now invisible and my vault remains wholly independent.