Web Publishing with Quartz

Quartz transforms your Obsidian vault into a fast, searchable static website. This guide covers setup, configuration, and deployment.

Why Quartz?

Quartz is a static site generator designed specifically for Obsidian vaults.

Benefits:

  • No modifications to your markdown files required
  • Respects Obsidian syntax: Wikilinks, embeddings, frontmatter
  • Fast and lightweight: Static HTML, no database
  • Full-text search: Client-side, no server needed
  • Graph view: Visual document relationships
  • Free hosting: GitHub Pages, Netlify, Vercel
  • Mobile-friendly: Responsive design
  • Dark mode: Built-in theme switching

Garage Project: Published at https://yourdomain.github.io/garage

Prerequisites

Before starting:

  • Obsidian vault set up and organized
  • Git installed (https://git-scm.com)
  • Node.js v18.14+ installed (https://nodejs.org)
  • GitHub account (for hosting)
  • Basic terminal/command line familiarity

Installation

1. Initialize Git Repository

If not already done:

cd /path/to/your/vault
git init
git add .
git commit -m "Initial commit"

2. Create GitHub Repository

  1. Go to https://github.com/new
  2. Repository name: garage (or your project name)
  3. Public or Private (your choice)
  4. Don’t initialize with README (you already have content)
  5. Create repository

3. Connect Local to GitHub

git remote add origin https://github.com/yourusername/garage.git
git branch -M main
git push -u origin main

4. Install Quartz

In your vault directory:

npx quartz@latest create content

When prompted:

  • “Choose how Quartz should resolve links”: Shortest path (matches Obsidian)
  • “Choose how to initialize content”: Empty Quartz (you already have content)

This creates:

  • quartz/ directory with Quartz configuration
  • quartz.config.ts - Main configuration file
  • quartz.layout.ts - Layout configuration

5. Verify Structure

Your vault should now look like:

your-vault/
├── content/              # Your existing content
│   ├── index.md
│   ├── 10-Planning/
│   ├── 20-Design/
│   └── ...
├── quartz/              # Quartz framework
│   ├── components/
│   ├── plugins/
│   └── ...
├── quartz.config.ts     # Quartz configuration
├── quartz.layout.ts     # Layout configuration
├── package.json
└── .git/

Configuration

Basic Configuration

Edit quartz.config.ts:

import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
 
const config: QuartzConfig = {
  configuration: {
    pageTitle: "Garage Construction Project",  // Site title
    enableSPA: true,
    enablePopovers: true,
    analytics: {
      provider: "google",  // Optional: Google Analytics
      tagId: "G-XXXXXXXXXX",
    },
    baseUrl: "yourusername.github.io/garage",  // Your GitHub Pages URL
    ignorePatterns: [
      "private",
      "Templates",
      ".obsidian",
      "ChatGPT Summaries",  // Exclude from web
    ],
    theme: {
      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)",
        },
        darkMode: {
          light: "#161618",
          lightgray: "#393639",
          gray: "#646464",
          darkgray: "#d4d4d4",
          dark: "#ebebec",
          secondary: "#7b97aa",
          tertiary: "#84a59d",
          highlight: "rgba(143, 159, 169, 0.15)",
        },
      },
    },
  },
  plugins: {
    transformers: [
      Plugin.FrontMatter(),
      Plugin.TableOfContents(),
      Plugin.CreatedModifiedDate({
        priority: ["frontmatter", "filesystem"],
      }),
      Plugin.SyntaxHighlighting(),
      Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
      Plugin.GitHubFlavoredMarkdown(),
      Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
      Plugin.Latex({ renderEngine: "katex" }),
      Plugin.Description(),
    ],
    filters: [Plugin.RemoveDrafts()],
    emitters: [
      Plugin.AliasRedirects(),
      Plugin.ComponentResources({ fontOrigin: "googleFonts" }),
      Plugin.ContentPage(),
      Plugin.FolderPage(),
      Plugin.TagPage(),
      Plugin.ContentIndex({
        enableSiteMap: true,
        enableRSS: true,
      }),
      Plugin.Assets(),
      Plugin.Static(),
      Plugin.NotFoundPage(),
    ],
  },
}
 
export default config

Layout Configuration

Edit quartz.layout.ts to customize page layout:

import { PageLayout, SharedLayout } from "./quartz/cfg"
import * as Component from "./quartz/components"
 
// Components shared across all pages
export const sharedPageComponents: SharedLayout = {
  head: Component.Head(),
  header: [],
  footer: Component.Footer({
    links: {
      GitHub: "https://github.com/yourusername/garage",
    },
  }),
}
 
// Components for pages displaying a single page
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.DesktopOnly(Component.Explorer()),
  ],
  right: [
    Component.Graph(),
    Component.DesktopOnly(Component.TableOfContents()),
    Component.Backlinks(),
  ],
}
 
// Components for pages that list multiple pages
export const defaultListPageLayout: PageLayout = {
  beforeBody: [Component.ArticleTitle()],
  left: [
    Component.PageTitle(),
    Component.MobileOnly(Component.Spacer()),
    Component.Search(),
    Component.Darkmode(),
  ],
  right: [],
}

.gitignore Configuration

Create/update .gitignore:

# Obsidian
.obsidian/workspace*
.obsidian/cache
.trash/

# Quartz
node_modules/
public/
.quartz-cache/

# OS
.DS_Store
Thumbs.db

# Private
private/
Templates/

Building Your Site

Local Preview

Test your site locally before publishing:

npx quartz build --serve

Then open http://localhost:8080 in your browser.

What to check:

  • All pages render correctly
  • Links work (especially wikilinks)
  • Images display
  • Search works
  • Graph view shows relationships
  • Mobile view looks good

Build for Production

When ready to publish:

npx quartz build

This generates static files in the public/ directory.

Deployment to GitHub Pages

Automatic deployment on every push:

  1. Create workflow file: .github/workflows/deploy.yml
name: Deploy Quartz site to GitHub Pages
 
on:
  push:
    branches:
      - main  # or your default branch
 
permissions:
  contents: read
  pages: write
  id-token: write
 
concurrency:
  group: "pages"
  cancel-in-progress: false
 
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Fetch all history for git info
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
 
      - name: Install dependencies
        run: npm ci
 
      - name: Build Quartz
        run: npx quartz build
 
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: public
 
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
  1. Enable GitHub Pages:

    • Go to repository Settings → Pages
    • Source: GitHub Actions
  2. Push to GitHub:

git add .
git commit -m "Add Quartz configuration and GitHub Actions workflow"
git push
  1. Monitor deployment:
    • Go to Actions tab in GitHub
    • Watch deployment progress
    • Site will be live at https://yourusername.github.io/garage

Option 2: Manual Deployment

If you prefer manual control:

# Build the site
npx quartz build
 
# Deploy to gh-pages branch
npx quartz deploy

Customization

Homepage Customization

Create a clean content/index.md for web visitors:

---
title: Garage Project Index
type: index
---
 
# Garage Construction Project
 
Documentation for a 24'x40'x10' three-car garage with second-floor workshop.
 
## Current Build Status
 
**Latest milestone (Oct 22, 2025):**
- ✓ Under-slab insulation complete (2" Creatherm R-10)
- ✓ PEX radiant heating loops installed
- ⧗ Pressure testing in progress
- Pending: Concrete pour
 
**🎥 [[50-Build/Construction Videos]]** — Watch time-lapse videos!
 
## Quick Links
 
- [[10-Planning/Timeline]] - Project timeline
- [[50-Build/Initial Build]] - Build progress
- [[30-Vendors & Contacts/Products Used]] - Product specifications
- [[10-Planning/Decisions Log]] - Major decisions
 
## Project Overview
 
- **Size**: 24' x 40' x 10' (960 sq ft main floor)
- **Features**: 2-post lift bay, radiant floor heat, second-floor workshop
- **Timeline**: Started October 2025
- **Budget**: $68,400 contract + additional
 
---
 
*For full project details, browse the sections in the sidebar.*

Custom CSS

Create quartz/styles/custom.scss:

// Custom styles for your site
 
// Wider content area
.center {
  max-width: 900px;  // Default is 750px
}
 
// Highlight boxes for important info
.callout {
  background: var(--lightgray);
  border-left: 4px solid var(--secondary);
  padding: 1rem;
  margin: 1rem 0;
}
 
// Photo galleries
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
  margin: 2rem 0;
 
  img {
    width: 100%;
    height: 250px;
    object-fit: cover;
    border-radius: 8px;
  }
}

Import in quartz/styles/base.scss:

@import "custom";

Excluding Content from Web

Use ignorePatterns in quartz.config.ts:

ignorePatterns: [
  "private",           // Entire folder
  "Templates",         // Template files
  ".obsidian",         // Obsidian config
  "**/*-private.md",   // Files matching pattern
  "ChatGPT Summaries", // AI drafts
],

Or use frontmatter in individual files:

---
title: Private Planning Notes
draft: true  # Excluded from build
---

Updating Your Site

Regular Updates

Your workflow:

  1. Make changes in Obsidian (update documents, add photos, etc.)
  2. Commit changes:
git add .
git commit -m "docs: add concrete pour progress photos"
git push
  1. GitHub Actions automatically rebuilds and deploys
  2. Site updates in 2-3 minutes

Monitoring

  • Check Actions tab for build status
  • Review build logs if errors occur
  • Test locally first for major changes

Advanced Features

Custom Domain

Use your own domain instead of username.github.io:

  1. Purchase domain (e.g., from Namecheap, Google Domains)
  2. Configure DNS:
    • Add CNAME record pointing to yourusername.github.io
  3. Update GitHub:
    • Settings → Pages → Custom domain
    • Enter your domain
  4. Update quartz.config.ts:
baseUrl: "garageproject.com",

Password Protection

GitHub Pages doesn’t support password protection natively.

Options:

  • Private repository: Only collaborators can see
  • Deploy to Netlify/Vercel: Offers password protection
  • Use CloudFlare Access: Add authentication layer

Analytics

Track visitors with Google Analytics:

analytics: {
  provider: "google",
  tagId: "G-XXXXXXXXXX",
},

Or Plausible (privacy-focused):

analytics: {
  provider: "plausible",
},

Search Customization

Quartz includes client-side search. Customize in layout:

Component.Search({
  enablePreview: true,  // Show content preview
}),

Troubleshooting

Build Fails

Check:

  • Node.js version (must be v18.14+)
  • No special characters in filenames
  • All images referenced exist
  • Frontmatter YAML is valid

Common fix:

rm -rf node_modules .quartz-cache
npm install
npx quartz build

Problem: Wikilinks not resolving

Solution: Verify quartz.config.ts:

markdownLinkResolution: "shortest"

Problem: Links to sections broken

Solution: Use proper heading anchors:

[[Document Name#section-heading|Section Heading]]

Images Not Displaying

Check:

  • Images in content/ directory (not .obsidian/)
  • Correct relative paths
  • No spaces in image filenames (or use %20)

Fix:

![Description](pictures/image-name.jpg)

Slow Build Times

For large vaults:

// Exclude large folders from graph
ignorePatterns: [
  "private",
  "archive",
  "ChatGPT Summaries",
],

Best Practices

Content Strategy

Separate Obsidian and Web content if needed:

  • 00-Index.md: Full Obsidian dashboard with Dataview
  • content/index.md: Clean web homepage

Use frontmatter for web-specific settings:

---
title: Clean Web Title
description: SEO-friendly description
tags: [public, important]
---

Link strategy:

  • Internal links: Use wikilinks [[Document]]
  • External links: Standard markdown [Text](URL)

Performance

  • Optimize images: Resize large photos
  • Limit embedding: Don’t embed entire documents unnecessarily
  • Use excerpts: Long documents load slowly

SEO

---
title: Garage Construction - 24x40 Workshop Build
description: Complete documentation of custom garage construction with photos and specifications
tags: [garage, construction, diy, workshop]
---

Example: Garage Project Setup

The actual Garage Project setup:

// quartz.config.ts
const config: QuartzConfig = {
  configuration: {
    pageTitle: "Garage Project",
    baseUrl: "yourusername.github.io/garage",
    ignorePatterns: [
      "Templates",
      ".obsidian",
      "ChatGPT Summaries",
    ],
  },
}

Result: Clean, fast documentation site with:

  • All construction progress
  • Photo documentation
  • Time-lapse videos
  • Product specifications
  • Decision history
  • Searchable content

Next Steps

With your site published:

05 - AI Integration and Automation - Use AI to accelerate documentation

06 - Advanced Features and Best Practices - Master advanced techniques

Resources


The Garage Project site is built and deployed using these exact steps.