Overview
Quartz builds to a public/ directory of static files that any static host serves. Set baseUrl in quartz-config before the first deploy. This page is the reference for per-host settings and gotchas; the end-to-end walkthroughs are deploy-quartz-site and deploy-a-static-site-to-cloudflare-pages.
Deploy to GitHub Pages with a build job and a deploy job
# .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@v7
with: { fetch-depth: 0 }
- uses: actions/setup-node@v7
with: { node-version: 22 }
- run: npm ci
- run: npx quartz build
- uses: actions/upload-pages-artifact@v5
with: { path: public }
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5fetch-depth: 0givesCreatedModifiedDatethe full git history for page dates.pages: writeandid-token: writeare required bydeploy-pages; leaving either out fails the deploy.cancel-in-progress: falsekeeps a second push from aborting a deploy in flight.workflow_dispatchlets you redeploy without a commit, for example after enabling HTTPS in the Pages settings.- In the repository settings, set Pages source to “GitHub Actions”. Action majors are tracked in github-actions; Pages settings are in github-pages.
Use Plugin.CNAME() for a custom domain
The emitter writes public/CNAME containing the hostname parsed from baseUrl, which is how GitHub Pages learns the custom domain.
- Do not put a
CNAMEfile inquartz/static/. It copies topublic/static/CNAME, where Pages ignores it. - Skip the plugin on a
user.github.io/repoproject URL; there is no custom domain to name. - After every build,
cat public/CNAMEshould print the bare domain.
Deploy to Cloudflare Pages from the Git repo
Connect the repository in the Cloudflare dashboard and set:
Build command: npx quartz build
Output directory: public
Pin Node with a NODE_VERSION environment variable, or a .node-version or .nvmrc file in the repo; Quartz 4.5.2 needs 22 or newer. Cloudflare builds every branch and gives each a preview URL. CreatedModifiedDate reads git history, so if displayed dates look wrong, check the host’s clone depth or set modified in frontmatter. DNS and TLS wiring is in cloudflare.
Deploy to Vercel from a subfolder or the repo root
{
"buildCommand": "npx quartz build",
"outputDirectory": "public",
"installCommand": "npm ci"
}Set the Node version through engines.node in package.json; Vercel applies it over the project setting. Pick a supported major of 22 or newer. For monorepos, set the project root to the Quartz folder. Full setup is in vercel.
Do not set immutable caching on Quartz’s own assets
Quartz emits its stylesheet and scripts under fixed names (index.css, prescript.js, postscript.js), with no content hash. A one-year immutable header on them would keep returning stale code after a deploy. Let the host’s default revalidation apply, or use a short max-age.
GitHub Pages does not let you set response headers. When you need custom caching or security headers, put Cloudflare in front and configure rules there; see cloudflare-cache-rules.