Skip to content
andrew.dunn.dev

Part 1: Obsidian + Quartz

Part 1 of the digital garden series.

The friction was in the wrong place

I used Hugo for years. It worked, but every time I wanted to write something I was context-switching into a static site generator’s world: creating files in the right directory, copying frontmatter, running a dev server. The blog wasn’t hard to maintain. It was hard to use.

The other problem was structural. Most static site generators want you to organize content into sections with defined taxonomies upfront. This works if you know what you’re building. It doesn’t work if the point is to figure that out over time.

Writing where I already write

I was already using Obsidian for private notes, daily journals, and draft capture on my phone. Markdown files in directories, no database, no proprietary format. The gap was that my published site lived somewhere else entirely.

Quartz closes this gap. It’s a static site generator built for Obsidian vaults, with native support for wikilinks, backlinks, and graph visualization. The key thing was using Quartz as a build tool rather than a framework: it’s cloned at CI time, pointed at the content directory via its -d flag, and produces static HTML. No Quartz files live in the vault.

build:
  stage: build
  script:
    - git clone --branch v4 --depth 1 https://github.com/jackyzha0/quartz.git /tmp/quartz
    - cd /tmp/quartz && npm ci
    - cp quartz/quartz.config.ts /tmp/quartz/
    - cp quartz/quartz.layout.ts /tmp/quartz/
    - cd /tmp/quartz && npx quartz build -d $CI_PROJECT_DIR/content -o $CI_PROJECT_DIR/public

Two config files (quartz/quartz.config.ts and quartz/quartz.layout.ts) are the only Quartz-specific artifacts in the repository. Everything else is content.

QUARTZ AS A BUILD TOOLRepositorythe Obsidian vaultcontent/markdown, wikilinksquartz/config.ts + layout.tsNO QUARTZ SOURCE HERECI jobephemeral, per pushClone Quartz v4—depth 1 into /tmpCopy two configsvault to /tmp/quartzRun the build-d content -o publicpublic/static HTMLcontentHTML

Quartz never lives in the vault. The repository carries the content and two config files, and the build job clones Quartz into a temporary directory, points it at the content directory, and hands back static HTML.

Less taxonomy, less friction

Early iterations had elaborate frontmatter schemas: type enums for experiences (book, film, music, place, ride), multi-section templates, sequence-numbered observations. All of it went in the trash. Upfront taxonomy creates friction at exactly the wrong moment: when you want to capture something, not classify it.

What replaced it: title, date, tags. Connections between notes happen through wikilinks in the text, not metadata hierarchies. This is the Zettelkasten insight: Niklas Luhmann organized his slip box by sequence and connected slips through references. The structure emerged from the connections, not from the filing system. The graph that Quartz renders grows from the writing rather than being imposed before it.

Mobile capture

I capture notes and URLs on my phone into a private/inbox/ directory. Obsidian Sync delivers them across devices, so they’re waiting on the desktop when I sit down to write. Processing happens there, where I have git and AI assistance (via OpenCode).

private/ is Sync-only (gitignored), published content is both Sync and git, media is Git LFS only. This keeps Obsidian Sync and git from conflicting on the same files.

AI as co-author

The other thing that changed was incorporating AI into the authoring workflow, not as a generator but as a collaborator that can search the corpus and suggest wikilinks I’d miss. The graph grows faster because the model can hold more of the corpus in context than I can. This is covered in part 4.

Next: Part 2: Workers + R2