Overview

Quartz 4 plus GitHub Pages plus Cloudflare is the cheapest reliable way to host a public knowledge base. This guide ships a Quartz vault to https://yourdomain.com end to end: fork, configure, wire the Actions workflow, point DNS, and prove the site responds. Total time is under an hour on a fresh GitHub account.

Prerequisites

  • Node 22 installed locally. Check with node -v.
  • A GitHub account with the ability to enable Pages on a public repo.
  • A domain you control, with access to its DNS records. See namecheap-dns if the domain is on Namecheap.
  • Local Quartz running. See run-a-quartz-static-site-locally for the local-only path.

Steps

1. Fork or clone Quartz

git clone https://github.com/jackyzha0/quartz.git mysite
cd mysite
npm ci

Push the repo to your own GitHub account. Keep the default branch as main; the Actions workflow assumes it.

2. Set the baseUrl and write content

Edit quartz.config.ts. Set baseUrl to the bare domain, no protocol, no path.

configuration: {
  pageTitle: "yoursite",
  baseUrl: "yourdomain.com",
  ignorePatterns: ["private", "templates", ".obsidian"],
  // ...
}

Drop your markdown into content/. Every page needs YAML frontmatter; see quartz for the plugin set that pays rent.

3. Enable Plugin.CNAME() and remove any manual CNAME file

In quartz.config.ts, confirm Plugin.CNAME() is in the emitters list. Do not place a CNAME file under quartz/static/; Quartz writes the right one at build time from baseUrl.

emitters: [
  Plugin.ContentPage(),
  // ...
  Plugin.CNAME(),
],

Run npx quartz build and confirm public/CNAME prints your domain.

4. Add the deploy workflow

Create .github/workflows/deploy.yml.

name: Deploy
on:
  push:
    branches: [main]
  workflow_dispatch:
permissions:
  contents: read
  pages: write
  id-token: write
concurrency:
  group: pages
  cancel-in-progress: false
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 lets Quartz read git history for CreatedModifiedDate. The id-token: write permission is required for actions/deploy-pages@v4; omitting it produces a 403. See github-pages for the full rationale.

5. Enable Pages with the Actions source

In the repo, open Settings, Pages. Set Source to “GitHub Actions”. Do not pick “Deploy from a branch”; that runs an opaque Jekyll build.

Push to main. The Actions tab should show two jobs, both green within two minutes.

6. Point DNS to GitHub Pages

For the apex (yourdomain.com), add four A records pointing at GitHub’s Pages IPs: 185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153. For a subdomain, add one CNAME record to <user>.github.io.

If you front the site with Cloudflare, set SSL/TLS mode to Full strict. See cloudflare.

7. Set the custom domain and enforce HTTPS

In Settings, Pages: enter yourdomain.com in “Custom domain”. GitHub verifies DNS. Wait until the green check appears, then toggle “Enforce HTTPS”. Provisioning can take 15 minutes; do not flip the toggle before the DNS check passes.

Verify it worked

Three checks confirm the site is live and correctly configured.

# 1. DNS resolves to GitHub Pages IPs
dig +short yourdomain.com
 
# 2. HTTPS responds and the canonical content shows
curl -sI https://yourdomain.com | head -1
# expected: HTTP/2 200
 
# 3. The CNAME file deployed
curl -s https://yourdomain.com/CNAME
# expected: yourdomain.com

If all three pass, the deploy is done.

Common errors

  • public/CNAME is missing or wrong. Confirm Plugin.CNAME() is in the emitters list and baseUrl is the bare domain.
  • actions/deploy-pages@v4 returns 403. The workflow is missing id-token: write under permissions.
  • DNS resolves but HTTPS shows a certificate error. The Let’s Encrypt provisioning has not finished. Wait up to 24 hours; do not flip “Enforce HTTPS” early.
  • The custom domain keeps unsetting itself. The build is overwriting public/CNAME with empty content; see github-pages for the Plugin.CNAME() pattern.
  • 404 on every page when the site is at user.github.io/repo. Add --base /repo/ or move to an apex domain.