Quartz
7 min read

Open-source static site generator that turns a collection of Markdown notes (e.g. an Obsidian vault) into a fully-featured, interactive website. Features graph view, full-text search, backlinks, and wikilink support out of the box.

Setup

sh
git clone https://github.com/jackyzha0/quartz.git
cd quartz
npm i
npx quartz create # scaffold / initialize content directory
npx quartz build --serve # dev server at http://localhost:8080
npx quartz build # build static output into ./public
npx quartz sync --commit # sync content + commit + push

Point Quartz at an existing Obsidian vault by symlinking or placing the vault inside content/, or set contentFolder in the build step.

Project Layout

quartz/
├── quartz.config.ts # site configuration
├── quartz.layout.ts # page layout (components)
├── content/ # your Markdown files
│ └── index.md # homepage
├── quartz/static/ # static assets copied verbatim to /public
├── quartz/styles/ # custom CSS (custom.scss is your entry point)
└── public/ # generated output (git-ignored)

Configuration (quartz.config.ts)

ts
import { QuartzConfig } from "./quartz/cfg";
import * as Plugin from "./quartz/plugins";

const config: QuartzConfig = {
configuration: {
pageTitle: "My Knowledge Base",
pageTitleSuffix: " – My Site",
enableSPA: true, // single-page app routing
enablePopovers: true, // hover preview popups
baseUrl: "example.com", // no protocol, no trailing slash
ignorePatterns: ["private", "templates", ".obsidian"],
defaultDateType: "modified", // "created" | "modified" | "published"
locale: "en-US", // affects date formatting
analytics: null,
// analytics: { provider: "plausible" }
// analytics: { provider: "google", tagId: "G-XXXXXXX" }
// analytics: { provider: "umami", websiteId: "...", host: "..." }
// analytics: { provider: "goatcounter", websiteId: "..." }

theme: {
fontOrigin: "googleFonts", // "googleFonts" | "local"
cdnCaching: true,
typography: {
header: "Schibsted Grotesk",
body: "Source Sans Pro",
code: "IBM Plex Mono",
},
colors: {
lightMode: {
light: "#faf8f8",
lightgray: "#e5e5e5",
gray: "#b8b8b8",
darkgray: "#4e4e4e",
dark: "#2b2b2b",
secondary: "#284b63",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
textHighlight: "#fff23688",
},
darkMode: {
light: "#161618",
lightgray: "#393639",
gray: "#646464",
darkgray: "#d4d4d4",
dark: "#ebebec",
secondary: "#7b97aa",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
textHighlight: "#b3aa0288",
},
},
},
},

plugins: {
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "git", "filesystem"],
}),
Plugin.SyntaxHighlighting({
theme: { light: "github-light", dark: "github-dark" },
keepBackground: false,
}),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(),
Plugin.CrawlLinks({
markdownLinkResolution: "shortest",
prettyLinks: true,
}),
Plugin.Description(),
Plugin.Latex({ renderEngine: "katex" }),
],
filters: [Plugin.RemoveDrafts()],
emitters: [
Plugin.AliasRedirects(),
Plugin.ComponentResources(),
Plugin.ContentPage(),
Plugin.FolderPage(),
Plugin.TagPage(),
Plugin.ContentIndex({ enableSiteMap: true, enableRSS: true }),
Plugin.Assets(),
Plugin.NotFoundPage(),
],
},
};

export default config;

Layout (quartz.layout.ts)

Controls which components appear where on the page.

ts
import { PageLayout, SharedLayout } from "./quartz/cfg";
import * as Component from "./quartz/components";

// Shared across all page types
export const sharedPageComponents: SharedLayout = {
head: Component.Head(),
header: [],
afterBody: [],
footer: Component.Footer({
links: { GitHub: "https://github.com/you/repo" },
}),
};

// Layout for regular content pages
export const defaultContentPageLayout: PageLayout = {
beforeBody: [
Component.Breadcrumbs(),
Component.ArticleTitle(),
Component.ContentMeta(),
Component.TagList(),
],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
Component.ReaderMode(),
Component.DesktopOnly(Component.Explorer()),
],
right: [
Component.Graph(),
Component.DesktopOnly(Component.TableOfContents()),
Component.DesktopOnly(Component.Backlinks()),
Component.RecentNotes({ title: "Recent", limit: 5 }),
],
};

// Layout for folder/tag index pages
export const defaultListPageLayout: PageLayout = {
beforeBody: [
Component.Breadcrumbs(),
Component.ArticleTitle(),
Component.ContentMeta(),
],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.Explorer()),
],
right: [],
};

Available Components

ComponentDescription
Component.ArticleTitle()Page h1 title
Component.Backlinks()Pages that link to this page
Component.Breadcrumbs()Navigation breadcrumb trail
Component.Comments()Comment section (Giscus, Utterances, Cusdis)
Component.ContentMeta()Date, reading time, word count
Component.Darkmode()Light/dark toggle
Component.Explorer()Collapsible file-tree sidebar
Component.Footer({ links })Site footer with links
Component.Graph()Interactive local/global graph view
Component.Head()<head> metadata and resources
Component.PageTitle()Site title (links to home)
Component.ReaderMode()Toggle to strip sidebar/decorations
Component.RecentNotes({ title, limit, filter, sort })List of recently modified notes
Component.Search()Full-text search bar
Component.Spacer()Empty flex spacer
Component.TableOfContents()In-page TOC from headings
Component.TagList()Tags from front matter
Component.DesktopOnly(C)Wrap to show only on desktop
Component.MobileOnly(C)Wrap to show only on mobile

Plugins Reference

Transformers

Process and transform Markdown content before rendering.

PluginKey OptionsDescription
FrontMatter()delimiters, languageParse YAML/TOML front matter
CreatedModifiedDate()priority: ["frontmatter","git","filesystem"]Set created/modified dates
SyntaxHighlighting()theme: {light, dark}, keepBackgroundCode block highlighting via Shiki
ObsidianFlavoredMarkdown()enableInHtmlEmbed, parseTags, parseBlockReferences, enableYouTubeEmbedWikilinks, callouts, comments, embeds
GitHubFlavoredMarkdown()linkHeadingsTables, task lists, strikethrough, autolinks
TableOfContents()maxDepth, minEntries, showByDefault, collapseByDefaultAuto-generate TOC from headings
CrawlLinks()markdownLinkResolution, prettyLinks, openLinksInNewTab, externalLinkIconResolve internal links, add external icons
Description()descriptionLengthAuto-generate page descriptions
Latex()renderEngine: "katex"|"mathjax", customMacrosMath rendering
HardLineBreaks()—Treat single newlines as <br>
Citations()bibliographyFile, cslBibTeX citation support
RoamFlavoredMarkdown()—Roam Research {{roam/links}} compatibility
OxHugoFlavoredMarkdown()wikilinks, removePredefinedAnchor, replaceFigureWithMdImgOxHugo Hugo export compatibility

Filters

Exclude pages from output.

PluginDescription
RemoveDrafts()Exclude pages with draft: true in front matter
ExplicitPublish()Only include pages with publish: true in front matter

Emitters

Generate output files.

PluginDescription
ContentPage()Render each note as an HTML page
FolderPage()Index page for each folder
TagPage()Index page for each tag
ContentIndex({ enableSiteMap, enableRSS })Search index, sitemap.xml, rss.xml
AliasRedirects()Redirect pages for aliases front matter
ComponentResources()Bundle and emit CSS/JS
Assets()Copy static assets from quartz/static/
NotFoundPage()404 page
CNAME()CNAME file for GitHub Pages custom domain

Front Matter

yaml
---
title: "Override Page Title"
description: "Short description for SEO and social previews"
date: 2024-01-15 # created date (ISO 8601)
created: 2024-01-15
modified: 2024-06-01
published: 2024-06-01 # use with defaultDateType: "published"
tags:
- tag1
- tag2
aliases: # create redirect pages from these slugs
- old-page-name
- another-alias
draft: true # excluded by RemoveDrafts filter
publish: false # excluded by ExplicitPublish filter
cssclasses: # add classes to <body>
- wide-page
- no-toc
socialImage: "images/cover.png" # Open Graph / Twitter card image
lang: en # language for i18n
---

Features


Enabled by ObsidianFlavoredMarkdown(). Supports:

• [[Page Name]] and [[Page Name|Display Text]]
• [[#Heading]] anchor links
• ![[image.png]] and ![[note.md]] embeds
• %%Comments%% (stripped from output)
• Callouts: > [!note], > [!warning], > [!tip], etc.
• Block references: [[page#^blockid]]

Graph View

Interactive node graph showing note connections. Configure in layout:

ts
Component.Graph({
localGraph: {
drag: true,
zoom: true,
depth: 1,
scale: 1.1,
repelForce: 0.5,
centerForce: 0.3,
linkDistance: 30,
fontSize: 0.6,
opacityScale: 1,
showTags: true,
removeSelfLinks: true,
},
globalGraph: {
drag: true,
zoom: true,
depth: -1,
scale: 0.9,
repelForce: 0.5,
},
});


Powered by FlexSearch. Works offline (index is embedded in the page). Enabled by ContentIndex() emitter + Search() component. Supports fuzzy matching.

Explorer

Collapsible file-tree sidebar. Configure sort order, depth, and which folders are open by default:

ts
Component.Explorer({
folderClickBehavior: "collapse", // "collapse" | "link"
folderDefaultState: "collapsed", // "collapsed" | "open"
useSavedState: true,
sortFn: (a, b) => {
/* custom sort */
},
filterFn: (node) => node.name !== "private",
mapFn: (node) => node,
});

Popover Previews

Hover over a wikilink to see a preview of the linked page. Enabled by enablePopovers: true in config.


Shows all notes that link to the current page. Add Component.Backlinks() to the layout.

LaTeX

Add Plugin.Latex({ renderEngine: "katex" }) to transformers. Use $inline$ and $$block$$ math.

Mermaid Diagrams

Supported via GitHubFlavoredMarkdown(). Use fenced code blocks with mermaid language tag.

Syntax Highlighting

Via Shiki. Supports 100+ languages and themes. Configure light/dark themes separately:

ts
Plugin.SyntaxHighlighting({
theme: { light: "github-light", dark: "one-dark-pro" },
keepBackground: false,
});

RSS Feed

Enabled by ContentIndex({ enableRSS: true }). Feed published at /index.xml.

Sitemap

Enabled by ContentIndex({ enableSiteMap: true }). Published at /sitemap.xml.

Social Media Preview Cards

Set socialImage in front matter or Quartz will auto-generate an OG image from the page title and description.

Comments

Add Component.Comments() to afterBody. Supports Giscus (GitHub Discussions), Utterances, and Cusdis:

ts
Component.Comments({
provider: "giscus",
options: {
repo: "you/repo",
repoId: "R_xxx",
category: "Announcements",
categoryId: "DIC_xxx",
},
});

Private Pages

Two approaches:

• Add draft: true + use RemoveDrafts() filter
• Use ExplicitPublish() filter and only add publish: true to pages you want public
• Add folder names to ignorePatterns in config

Internationalization

Set locale in config. Controls date formatting. Per-page language via lang in front matter.

Reader Mode

Add Component.ReaderMode() to layout. Hides sidebars and decorations for focused reading.

Recent Notes

ts
Component.RecentNotes({
title: "Recent Notes",
limit: 5,
filter: (f) => f.slug !== "index",
sort: (f1, f2) =>
(f1.dates?.modified ?? 0) > (f2.dates?.modified ?? 0) ? -1 : 1,
});

Custom Styles

Edit quartz/styles/custom.scss:

scss
// Override CSS variables
:root[saved-theme="light"] {
--light: #fafafa;
--secondary: #4a6fa5;
}

// Wide page layout for specific pages
.wide-page article {
max-width: 900px;
}

// Style callouts
.callout[data-callout="custom"] {
--color: #5b9bd5;
--border: #5b9bd5;
}

Deployment

GitHub Pages

Create .github/workflows/deploy.yml:

yaml
name: Deploy Quartz to Pages
on:
push:
branches: [v4]
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 # needed for git-based dates
- 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
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/deploy-pages@v4
id: deployment

Cloudflare Pages

Build command: npx quartz build
Output directory: public
Node version: 22 (set in environment variables)

Vercel / Netlify

Build command: npx quartz build
Publish directory: public