Obsidian stores your notes as plain markdown in a plain folder. That is the
whole reason this is possible: there is no database to export and no proprietary
format to reverse-engineer. A static site generator can read that folder
directly.
Quartz is the one built specifically for it. It
speaks Obsidian’s dialect natively — [[wikilinks]], transclusions, callouts,
nested tags — and gives you back the parts of Obsidian you actually miss on the
web: backlinks, a graph view, hover previews, and full-text search.
This post walks the whole path end to end. The example vault it produces is
published right here on this site, so every claim below
is checkable by clicking.
What you end up with
A static site — plain HTML, CSS and JS, no server-side runtime, no database —
that you can host anywhere, including for free. Rebuilt with one command
whenever you write something new.
Why Quartz and not something else
Option
Cost
Wikilinks
Backlinks + graph
Your own HTML/CSS
Obsidian Publish
$10/mo
yes
yes
limited
Quartz
free
yes
yes
full
Hugo / Jekyll
free
needs plugins
no
full
Docusaurus / MkDocs
free
partial
no
full
Obsidian Publish is the zero-effort answer and there is nothing wrong with
paying for it. Quartz costs you an afternoon once and nothing after that, and
because the output is just files you are never locked in. The generic static
site generators are excellent tools that happen to be a bad fit here — they
treat [[Some Note]] as literal text unless you go plugin shopping.
Before you start
Node.js 22 or newer. Check with node --version. Quartz 4.5 declares
node >= 22 and npm >= 10.9.2; older versions fail in confusing ways.
git. Used both to install Quartz and to deploy it.
An Obsidian vault, or the willingness to make one. The vault does not need
to be tidy — you will see below how to publish part of it.
Step 1 — Install Quartz
Quartz is used by cloning it, not by installing it as a dependency. Your site
lives inside the clone.
That leaves you with a few directories that matter:
my-site/
├── content/ ← your vault goes here
├── quartz/ ← the generator itself; don't edit
├── public/ ← build output; this is the website
├── quartz.config.ts ← what the site is
└── quartz.layout.ts ← where things sit on the page
Why clone instead of install?
Quartz’s configuration is TypeScript, and its components are real source
files you are meant to edit. Cloning gives you that access. The cost is that
upgrading means pulling from upstream and resolving conflicts in your config —
npx quartz update does this for you.
Step 2 — Get your vault into content/
Everything under content/ becomes the site. There are three ways to put it
there, and the right one depends on how you work.
Copy it. Simplest, and fine if you publish in deliberate batches.
cp -r "/path/to/Your Vault" content/
Symlink it. Your vault stays where Obsidian expects it, and Quartz reads it
live. On Windows this needs an elevated shell:
Make the vault a git submodule. The right answer if your vault is already
its own repository. Your notes keep their own history, separate from the site’s.
Whichever you pick, Obsidian can open content/ directly as a vault. That
is worth doing — you get Obsidian’s editor, graph and search over exactly the
files that will be published, with no sync step to forget.
The example vault on this site is a real one: content/example-vault contains
its own .obsidian folder and opens in Obsidian unmodified.
Step 3 — Configure the site
Open quartz.config.ts. Most of it can wait; these four fields cannot.
No protocol, no trailing slash. eecs.blog, not https://eecs.blog/. It can
include a path if your site lives in a subdirectory — example.com/notes.
Get it wrong and the site will still build and still look fine locally. What
breaks is everything that needs an absolute URL: RSS, the sitemap, OpenGraph
preview cards, and the canonical tags search engines read. These fail
silently, which is why this is worth double-checking before you deploy.
Two more worth knowing about:
analytics defaults to { provider: "plausible" }. Unless you actually
run Plausible, set it to null so your visitors aren’t pinging a third party
for nothing.
ignorePatterns is your privacy boundary. More on that next.
Step 4 — Decide what stays private
A vault usually contains things you never intended to show anyone. Quartz gives
you three independent mechanisms, and it is worth using more than one.
Per-note, by frontmatter. Add draft: true and the RemoveDrafts filter
drops that note at build time:
---title: Half-finished thoughtsdraft: true---
Per-folder, by config. Anything matching ignorePatterns never gets read:
By what you copy. If a folder never enters content/, it cannot leak.
Verify before you deploy, not after
public/ is the entire site. Grep it for something that should never be
public and confirm you get nothing back:
grep -ril "confidential" public/ | head
Deleting a note later does not un-publish it from caches, archives, or
whatever crawled it in the meantime.
This site’s example vault demonstrates the first mechanism: Analog/Noise in Amplifiers.md exists in the vault and in git, has draft: true, and has no
page here. The vault index links to the others and
deliberately does not link to that one.
Step 5 — What survives the trip
Most of Obsidian carries over untouched:
Feature
Works?
Notes
[[Wikilinks]]
yes
resolved by shortest unique path
[[Note|display text]]
yes
![[Embedded image]]
yes
any attachment folder layout
![[Note]] transclusion
yes
add a #Section suffix for one heading
Callouts
yes
all types, including collapsible
Nested tags
yes
#circuit/analog gets its own tag page
aliases: frontmatter
yes
emitted as real redirects
LaTeX
yes
KaTeX by default, MathJax available
Mermaid diagrams
yes
Code blocks
yes
syntax highlighting via Shiki
Footnotes, tables, task lists
yes
GFM
Dataview queries
no
render as raw code blocks
Community plugins
no
Excalidraw, Kanban, etc.
Canvas files
no
.canvas is not rendered
The two that hurt are Dataview and Excalidraw, because people build their whole
vault around them. Workarounds: replace Dataview index pages with hand-written
maps of content, and export Excalidraw drawings to SVG (Export as image → SVG) and embed the SVG instead. Both are one-time costs, and the SVG version is
better for the web anyway.
Attachments
Wherever your images live — Resources/, attachments/, alongside the notes
— Quartz finds them, as long as they are inside content/. If an image is
outside the vault, Obsidian will show it and Quartz will not.
Step 6 — Preview it
npx quartz build --serve
Open http://localhost:8080. This is a hot-reloading dev server: edit a note in
Obsidian, save, and the browser updates. It is a preview server only — do not
put it on the internet.
Useful flags: -d to point at a different content folder, -o for a different
output folder, --port if 8080 is taken, -v when something is wrong and you
need to see why.
Step 7 — Build
npx quartz build
This writes the finished site to public/. That folder is self-contained —
HTML, CSS, JS, images, an RSS feed and a sitemap. Any static host can serve it.
Step 8 — Deploy
Pick one. All of these assume your Quartz clone is pushed to a git repository of
your own (npx quartz sync --no-pull does the first push).
Cloudflare Pages
Connect the repo, then set:
Option
Value
Build command
npx quartz build
Build output directory
public
Framework preset
None
Cloudflare shallow-clones by default, which breaks git-derived timestamps. If
you rely on those, use git fetch --unshallow && npx quartz build as the build
command instead.
Then set Settings → Pages → Source to GitHub Actions. For a custom domain,
point an apex domain at GitHub’s four A records
(185.199.108-111.153), or a subdomain via CNAME to
<username>.github.io.
Netlify or Vercel
Build command npx quartz build, publish directory public. Vercel
additionally needs a vercel.json at the repo root, or every URL will demand a
.html extension:
{ "cleanUrls": true}
Your own server
public/ is just files, so rsync is a complete deployment strategy:
npx quartz sync — commits and pushes your content, pulling in changes from
your other machines first.
Your host rebuilds automatically.
If you self-host, replace step 3 with the rsync line above. Either way it is
one command, which is the point — a publishing pipeline you have to think about
is one you stop using.
When something goes wrong
A link renders as plain text instead of a link
The target note does not exist, or is a draft, or is inside an
ignorePatterns folder. Quartz leaves unresolved wikilinks as text rather
than producing a dead link. Check for a typo first, then check whether you
excluded the target.
Images work in Obsidian but not on the site
The image is outside content/. Obsidian can reach anywhere on your disk;
Quartz only reads its content folder. Move the attachment inside the vault.
RSS, sitemap, or social previews are broken
baseUrl is wrong. It should have no protocol and no trailing slash.
Every page shows today's date
Quartz reads dates from frontmatter first, then git history, then the
filesystem. Copying a vault resets filesystem timestamps, and shallow CI
clones destroy git history — hence fetch-depth: 0 in the workflow above.
The durable fix is a date: field in your frontmatter.
Wikilink syntax inside a code span renders wrong
Only bites you if you write about Obsidian. Quartz rewrites wikilinks in the
raw text before parsing, so it does not respect code spans or fenced blocks:
a literal [[Note]] with a #Section suffix comes out either transcluded or
with a stray backslash, depending on where it sits. Splitting the anchor into
its own code span — as the table above does — is the least ugly way out.
The build is slow
CustomOgImages renders a preview image per page and dominates build time.
Comment it out of the emitters list while you are iterating.
Worth the afternoon?
The setup is a couple of hours the first time. After that the marginal cost of
publishing a note is zero — you write in the same editor you already use, and
the site follows. Nothing about the arrangement is proprietary: it is a folder
of markdown, a build command, and a pile of static files.
Start with the example vault if you want to see the
shape of it before committing your own notes.