This Digital Garden is built with Obsidian and Quartz which is a static site generator built specifically for converting Obsidian vaults into websites.
I have a private repository notes.hamatti.org that starts with a clone of the Quartz repository and then adds my own configuration on top.
Building
I don’t want to store my Obsidian vault inside the tooling, so I have set up a build & development server scripts inside package.json:
"scripts" {
"update_recent": ",garden_recent",
"build": "npx quartz build --directory=/path/to/my/Obsidian/vault",
"serve": "npx quartz build --serve --port 8888 --directory=/path/to/my/Obsidian/vault",
"publish": "cd public/ && git add . && git commit -m \"Updates\" && git push origin main",
"build-and-publish": "npm run update_recent && npm run build && npm run publish",
}
When I want to publish a new version of my digital garden, I navigate to the project folder and run npm run build-and-publish. It will start a pipeline of actions:
- It runs
update_recentwhich updates Recently updated based on the time the notes were updated and show 25 most recently updated notes. See Create Obsidian recently updated note with Python. - Then it runs
buildwhich runs Quartz that turns the contents of this Obsidian vault into a website. - Then it runs
publishwhich navigates to the folder with static site Quartz just built us, adds all changes to git repository and pushes those changes to my remote repository. - When
mainbranch in the remote repository is updated, a webhook triggers a new build of the site into the website you’re reading this note in.
I really like this flow. All I pretty much ever need to do is to run one command from command line and everything else is done automatically. I still maintain the granular control to run any of these separately if I need.
While this approach requires manual building and publishing but like with my main website, I like that manual step and in case of publishing a public subset of private notes, I want to be able to manually check that I haven’t accidentally put a note into a wrong folder from where it would be picked up by automation to publish on the web.
External links
For digital gardens, I think it’s nice to visually indicate which links are internal links and which one lead to elsewhere. Quartz provides this as a configuration option for its CrawlLinks plugin but I turn that off and instead, I use CSS after pseudo-element. My main reason for this is to avoid the SVGs from showing up wonky in different RSS readers.
/*
The first and last fill values should be the same as the --secondary color
and prefixed with %23 (which stands for #).
The middle fill should be 'none' because it's the inside of the box
*/
a[href^="http"]::after {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewbox='0 0 12.2 12.2' width='14' height='14'%3E%3Cpath fill='%23284b63' d='M5.7 0v1.5h4L4.5 6.7l1 1.1 5.3-5.2v3.9h1.4V0z'/%3E%3Cpath fill='none' d='M3.4 6.7l3-2.9H1.5v7h7V5.9l-3 2.9z'/%3E%3Cpath fill='%23284b63' d='M8.5 5.9v4.9h-7v-7h4.9l1.5-1.6H0v10h10V4.4z'/%3E%3C/svg%3E");
margin-left: 0.25em;
}Internal links to missing pages
In Obsidian, I create links to other notes before I make those notes. Sometimes I make those notes right away, sometimes they are there to let me know through autocomplete that I’ve written about it before.
On a published site, those would all lead to 404 for missing note and that’s not a great UX.
There’s currently no way to configure this in Quartz but thanks to it being open source and people contributing, I found help in the GitHub issues. In contentPage.tsx, inside the emit method, inside the for (const [tree, file] of content) loop, I added
/** Until the end of visit(), this code snippet is from
* https://github.com/jackyzha0/quartz/issues/454#issuecomment-2408792538
* by auctumnus
*
* It removes all the links that would lead to missing pages, ie.
*
* [[Missing link]] when Missing link.md does not exist.
*/
const allSlugs = allFiles.map((f) => (f.slug ? resolveRelative(slug, f.slug) : ""))
visit(tree as Root, "element", (elem) => {
if (elem.tagName === "a" && elem.properties.href) {
const href = elem.properties.href.toString()
if (href.startsWith("#")) {
return
}
if (!allSlugs.includes(href as RelativeURL)) {
if (elem.properties.className === undefined) {
elem.properties.className = "dead-link"
} else if (Array.isArray(elem.properties.className)) {
if (elem.properties.className.includes("external")) {
return
}
elem.properties.className.push("dead-link")
} else if (typeof elem.properties.className === "string") {
if (elem.properties.className.includes("external")) {
return
}
elem.properties.className += " dead-link"
} else {
return
}
elem.tagName = "span"
}
}
})and added the missing imports. This changes the missing links from a tags to span tags, saving the end user from confusion and unnecessary navigation to 404 page.
Thoughts on Quartz
While in general I’m happy with the ease of use and the ability to configure things by changing the source code of Quartz, one thing that bothers me is the extensive use of client-side Javascript.
There’s a lot of inlined Javascript I haven’t yet been able to clean up and a lot of that causes git to be super slow because there each content change introduces changes into the inlined Javascript so calculating diffs becomes a chore to the computer.
On each individual page, there are thousands of incredibly long lines of inlined Javascript and CSS and I can’t figure out why or how I can get rid of them. A static site does not need this much extra weight.
As an experiment, in renderPage.tsx, I commented out contentIndexScript and staticResources.js. This seems to have removed a good few thousand lines of content and so far the only non-working part I could find was search which I removed gladly.
What if I can’t or don’t want to code?
Maggie Appleton has a great guide at Digital Gardening for Non-Technical Folks