I’ve finally found a blog format I like: Quartz

It works well with Obsidian (which I have installed and opened so far) and can handle my existing Markdown files. It seems to have more features than Emanote. Apart from Tiddlywiki this was probably the easiest blog setup I have done. It also works well with GitHub Pages hosting. I have written up my notes and then zhuzhed them up with AI. I am not going to generate any content with AI but why deny myself the useful formatting.


Prerequisites

Quartz requires Node ≥ v22 and npm ≥ v10.9.2. The cleanest way to get and manage Node versions is via nvm (Node Version Manager), a POSIX-compliant Bash script that installs per-user and lets you switch versions per-shell.

Install nvm

Check the nvm repo for the latest install-script version, then:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

The installer clones nvm into ~/.nvm and adds source lines to your shell profile (~/.bashrc, ~/.zshrc, etc.). To pick up the new shell function, either close and reopen the terminal or:

source ~/.bashrc

Verify with command -v nvm (note: which nvm won’t work — nvm is a sourced shell function, not an executable).

Install Node

nvm install 22
nvm use 22

To update Node 22.x in future, re-running nvm install 22 will pick up the latest patch release.


Install Quartz

Following the Quartz getting-started guide:

git clone https://github.com/jackyzha0/quartz.git
cd quartz
npm install
npx quartz create

The create step is interactive. My choices:

┌   Quartz v4.5.2

◇  Choose how to initialize the content in `/home/paul/quartz/content`
│  Empty Quartz

◇  Choose how Quartz should resolve links in your content.
│  Treat links as shortest path

└  You're all set!

“Shortest path” link resolution matches Obsidian’s default and means wikilinks like [[Causal Inference]] resolve correctly without needing a full path.

Preview locally

npx quartz build --serve

Output:

 Quartz v4.5.2
Cleaned output directory `public` in 958μs
Found 1 input files from `content` in 11ms
Parsed 1 Markdown files in 99ms
Emitted 15 files to `public` in 625ms
Started a Quartz server listening at http://localhost:8080

A mostly blank welcome page appears at localhost:8080. Broken/missing links route to a 404 page rather than breaking the build which was nice.


Project layout

The cloned quartz folder includes:

  • content/ — where all Markdown content lives.
  • quartz/static/ — static assets (HTML, JS, CSS, favicons). I think these are copied straight through at build time without processing, but I will need to check if anything is sanitised.

Configuration (quartz.config.ts)

The bits I changed:

  • pageTitle — site title.
  • analyticsProvider — set to null (no analytics).
  • localeen-GB.
  • baseUrlprcleary.github.io (no trailing slash, no protocol — Quartz adds those).
  • defaultDateTypecreated (alternatives: modified, published).
  • Typography — Google Fonts integration is trivial:
typography: {
  header: "IBM Plex Sans",
  body: "IBM Plex Sans",
  code: "IBM Plex Mono",
},
  • Colours — individually tweakable rather than locked to opinionated themes.
  • Transformers / filters / emitters — left as defaults for now

Adding content

  • Front page (content/index.md) currently shows a date which I will try to remove at some point.
  • Blog posts copied into content/blog/ are sorted alphabetically in the explorer, though shown in the correct order on the blog page - I am not bothered about that.
  • Syntax highlighting works out of the box.
  • Mermaid diagrams work out of the box and are zoomable which was nice.
  • KaTeX works out of the box for maths: .
  • Raw HTML files can be dropped into content/ and are served as-is.
  • Favicon: drop your own favicon as icon.png into quartz/static/.
  • Images: place alongside the post (e.g. inside content/blog/).
  • Don’t use colons in titles unless you have quoted the title.

Updating Quartz

The official upgrade path uses npx quartz update or a git remote pointing at jackyzha0/quartz. I cloned and merged history into my own repo to preserve commits, which means I can’t use the npx workflow.

Will need to think about how to update the blog software.


Deployment — GitHub Pages

Following the Quartz hosting guide: Created .github/workflows/deploy.yml adapted from the recommended workflow, with the trigger branch changed to main (since I’m working off main, not v4):

name: Deploy Quartz site to GitHub Pages
 
on:
  push:
    branches:
      - main

The remainder of the workflow follows the hosting guide.

Then:

  1. Settings → Pages → Source: GitHub Actions in the repo on github.com.
  2. Commit, push, and the action deploys to prcleary.github.io.

Bash shortcuts

I can just use blog to open a (blank or existing) blog post for today. I can use publish to push it to the Web. I can use mdvim "Firth" to sequentially open all Markdown files containing a particular string - useful for rationalising tags.

blog() {
  local blog_dir="$HOME/prcleary.github.io/content/blog"
  local date
  date=$(date +%F)   # yyyy-mm-dd format
  local file="$blog_dir/$date.md"
  vim "$file"
}
alias publish='cd ~/prcleary.github.io && git pull && git add . && git commit -m "Add content" && git push && cd -'
mdvim() {
    if [ -z "$1" ]; then
        echo "Usage: mdvim <search-string>" >&2
        return 1
    fi
 
    # Collect matching files into an array (null-delimited to handle weird filenames)
    local files=()
    while IFS= read -r -d '' f; do
        files+=("$f")
    done < <(grep -rilZ -F -- "$1" --include='*.md' .)
 
    if [ ${#files[@]} -eq 0 ]; then
        echo "No matching .md files found for: $1" >&2
        return 1
    fi
 
    vim "${files[@]}"
}