---
title: "Quartz Best Practices"
slug: "quartz"
category: "tooling"
tags: ["tooling", "quartz", "ssg", "obsidian"]
status: "stable"
last_updated: 2026-05-14
summary: "When to pick Quartz 4, the plugins worth enabling, the CNAME pattern that actually works, and the Actions deploy flow."
related: ["[[ops/github-pages]]", "[[tooling/obsidian]]", "[[seo/llms-txt]]"]
---

## 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 [[frontend/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.

```ts
// 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.

```ts
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 [[tooling/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 [[seo/llms-txt]] for the format itself and [[howto/ship-llms-txt]] for the end-to-end build steps.

## Deploy via Actions to GitHub Pages

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

```yaml
# .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 [[ops/github-pages]] for the GitHub Pages settings that pair with this workflow, [[tooling/github]] for branch protection on `main`, and [[howto/deploy-quartz-site]] for the end-to-end deploy walkthrough. For local iteration, see [[howto/run-a-quartz-static-site-locally]].

## Related

- [[ops/github-pages]]
- [[tooling/obsidian]]
- [[seo/llms-txt]]
- [[tooling/github]]
