Overview

Quartz 4 is a static site generator built around Obsidian-flavored markdown. It reads a vault, resolves wikilinks, and emits a fast HTML site. This page covers when to reach for Quartz, the quartz.config.ts knobs that matter, the CNAME trap that bites every new user, and the GitHub Actions flow that ships it.

Use Quartz when

Reach for Quartz when the site is an Obsidian-first knowledge base.

  • Personal or team knowledge bases authored in Obsidian.
  • Public reference vaults where wikilinks and backlinks are the primary navigation.
  • Hosted on GitHub Pages or Cloudflare Pages, deployed via Actions.

Pick a different SSG when the site is content-marketing-first with rich layouts (Astro), an interactive app (Next.js), or a single-author blog with a chronological feed (Hugo). See astro for the Astro option.

Pin Node 22 and the Quartz version

Node version drift causes silent build differences. Pin both.

  • package.json engines field: "node": "22".
  • The Actions workflow uses actions/setup-node@v4 with node-version: 22.
  • Track the Quartz version in package.json. Do not run npx quartz against a globally installed copy on local machines.

Enable the plugins that pay rent

Quartz ships with a long plugin list. The set below covers most public knowledge bases.

// quartz.config.ts
plugins: {
  transformers: [
    Plugin.FrontMatter(),
    Plugin.CreatedModifiedDate({ priority: ["frontmatter", "git"] }),
    Plugin.SyntaxHighlighting({ theme: { light: "github-light", dark: "github-dark" } }),
    Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
    Plugin.GitHubFlavoredMarkdown(),
    Plugin.TableOfContents(),
    Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
    Plugin.Latex({ renderEngine: "katex" }),
  ],
  filters: [Plugin.RemoveDrafts()],
  emitters: [
    Plugin.ContentPage(),
    Plugin.FolderPage(),
    Plugin.TagPage(),
    Plugin.ContentIndex({ enableSiteMap: true, enableRSS: true }),
    Plugin.Assets(),
    Plugin.Static(),
    Plugin.CNAME(),
  ],
}

ObsidianFlavoredMarkdown already handles Mermaid blocks; do not add a second Mermaid plugin. Latex covers math. ContentIndex produces /sitemap.xml and /index.xml (RSS). CNAME is the part most setups get wrong.

Use Plugin.CNAME() for custom domains

A CNAME file at the build root is how GitHub Pages knows the custom domain. Quartz ships an emitter that places it there.

  • Add Plugin.CNAME() to the emitters list and set configuration.baseUrl to the bare domain (no protocol, no path): "llmbestpractices.com".
  • Do not drop a CNAME file into quartz/static/. That ships to public/static/CNAME, which GitHub Pages ignores, and the custom domain silently breaks.
  • Verify after a build: cat public/CNAME should print the domain.

This is the single most common Quartz misconfiguration. Use the plugin.

Set baseUrl and ignorePatterns early

baseUrl controls canonical URLs, RSS links, and the sitemap. Get it wrong and SEO breaks.

const config: QuartzConfig = {
  configuration: {
    pageTitle: "llmbestpractices",
    baseUrl: "llmbestpractices.com",
    ignorePatterns: ["private", "templates", ".obsidian"],
    defaultDateType: "modified",
    theme: { /* ... */ },
  },
  plugins: { /* see above */ },
}

baseUrl is the bare domain; Quartz adds https:// itself. ignorePatterns keeps Obsidian workspace files and private notes out of the build. See obsidian for the matching vault layout.

Ship llms.txt from the content root

Quartz copies any markdown file at content/ into the build. Drop content/llms.txt and it ships to https://yourdomain/llms.txt.

  • Keep the file plain markdown with H2 sections per category.
  • Cross-link it from content/index.md so humans can find it.
  • See llms-txt for the format itself.

Deploy via Actions to GitHub Pages

The standard deploy flow runs Quartz on push to main and uploads the public/ directory.

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - run: npx quartz build
      - uses: actions/upload-pages-artifact@v3
        with: { path: public }
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: github-pages
    steps:
      - uses: actions/deploy-pages@v4

fetch-depth: 0 is required so Quartz can read git history for CreatedModifiedDate. See github-pages for the GitHub Pages settings that pair with this workflow, and github for branch protection on main.