June 17, 2025
Note: As of 2026, this site is set up differently. Perhaps i will write a follow-up.
This is how I set up this Blog, free of charge. All you need is a markdown editor, in my case Obsidian, and a Github account. We'll be able to edit the blog in markdown locally, and then create a static web page on Github Pages. You can keep your other notes seperated from the blog, we will only publish the <Vault>/Blog folder. We'll automate the publishing stage with a CI/CD pipeline and an Obsidian plugin. You'll be able to publish just by pressing a hotkey in Obsidian!
Besides Obsidian and Github Pages, a static site generator is used to create the HTML sources. We will use Quartz 4, which comes pre-configured with useful features, like an RSS-feed and a sitemap.
We will clone the current Quartz repository into our vault. This allows for customization of components and plugins. If you want to use a submodule, keep in mind to use --depth=2 later when cloning the repo. Otherwise just delete the nested .git folder. (Do not delete your main Git folder.) We will clone Quartz into the folder .github/quartz.
001mkdir .github && cd .github002git clone https://github.com/jackyzha0/Quartz.git003cd quartz004npm i005npx quartz create
In .github/Quartz/Quartz.config.ts, you should definitely change the fields
For further customization, refer to the documentation.
We will host the website in a separate Github repository for security-reasons. Create this repository, and then create an access token, so we can publish from the main repository. Upload the token as a secret in the main repository, that we can use in the pipeline later. There it's safely stored. You will need to create a new token, if you decide to create a new pipeline however. Go to Settings > Secrets > Actions and add the secret under the PAGES_DEPLOY_TOKEN. Lastly, enable Github Pages in the hosting-repository under Settings > Pages.
We can create the main CI/CD pipeline now. Github Actions Pipelines are stored in .github/workflows.
001name: Build and Deploy Quartz to External Pages Repo002003on:004 push:005 branches: [ "*" ]006007jobs:008 build:009 runs-on: ubuntu-latest010011 steps:012 - name: Checkout main repo013 uses: actions/checkout@v4014015 - name: Setup Node.js 22016 uses: actions/setup-node@v4017 with:018 node-version: 22019020 - name: Replace Quartz content with Blog folder021 run: |022 rm -rf .github/quartz/content/*023 mkdir -p quartz/content024 rsync -av --delete Blog/ .github/quartz/content/025026 - name: Install dependencies027 working-directory: .github/quartz028 run: npm install029030 - name: Build Quartz site031 working-directory: .github/quartz032 run: npx Quartz build033034 - name: Push to pages repo035 run: |036 ls037 cd .github/quartz/public038 git init039 git config user.name "github-actions[bot]"040 git config user.email "github-actions[bot]@users.noreply.github.com"041 git remote add origin https://x-access-token:${{ secrets.PAGES_DEPLOY_TOKEN }}@github.com/fabsch225/fabsch225.github.io.git042 git checkout -b main043 git add .044 git commit -m "Deploy Quartz site"045 git push -f origin main046
To easily push the blog from the Obsidian user interface, we can configure a plugin for Obsidian. This is not absolutely necessary, but speeds up your workflow. Also, this will serve as a practical back-up solution for your whole Vault. Disclaimer. Git needs to be installed on your machine, so this is only relevant for PC and Mac.
Obsidian plugins live in your vault, specifically in the .obsidian folder. We'll just create it there. Once we start using git, version control for our custom setup comes for free. We require the following folder-structure:
001.Obsidian/002└── plugins/003 └── git-automation/004 ├── main.js005 └── manifest.json
manifest.json will tell Obsidian the plugin metadata:
001{002 "id": "git-auto-push",003 "name": "Git Auto Push",004 "version": "1.0.0",005 "minAppVersion": "0.12.0",006 "description": "Automatically add, commit, and push your vault",007 "author": "Fabian Schuller",008 "authorUrl": "https://github.com/fabsch225",009 "main": "main.js"010}
In main.js, we configure the Obsidian-command, that will trigger the publishing process, and start a node child-process, that executes the git commands.
001const { Plugin, Notice } = require('obsidian');002const { exec } = require('child_process');003004module.exports = class GitAutoPushPlugin extends Plugin {005 async onload() {006 this.addCommand({007 id: 'git-auto-commit-push',008 name: 'Git Add, Commit (Date), and Push',009 callback: () => this.runGitCommands()010 });011 }012013 async runGitCommands() {014 const vaultPath = this.app.vault.adapter.getBasePath();015 const date = new Date().toISOString().split('T')[0];016017 const command = `018 cd "${vaultPath}" && \019 git add . && \020 git commit -m "${date}" && \021 git push022 `;023024 exec(command, (error, stdout, stderr) => {025 if (error) {026 console.error('Git command error:', error);027 new Notice('Git push failed: ' + error.message);028 return;029 }030 if (stderr) console.warn('Git stderr:', stderr);031 new Notice('Git commit and push successful!');032 });033 }034}035
Now, reload the plugins in the Obsidian app, and enable our custom plugin. Then press CTRL+P to open the command-prompt and search for the command Automatically add, commit, and push your vault. In the plugin's settings, you can also bind a hotkey to this command, if you so wish.
If you made it this far, you will notice that the Links between pages are broken. They each have the prefix Blog/, because it's like this in your main Obsidian Vault. For this, i propose a custom Quartz plugin. We will create a plugin, that transforms the links, so the Prefix is avoided. This only requires a regex to find those Links, and a search-and-replace Library to actually transform the pages.
Plugins are not difficult to set up, and this simple plugin opens the way for more advanced customization. Plugins in Quartz fall into 3 categories
| Plugin Type | Functionality | Exemplary Use-Case |
| Transformer | Alter or enrich each file by manipulating text, AST or resources | Render latex or create wordcount |
| Filter | Decide which file gets published | Skip drafts |
| Emitter | Produce final output (override default here) | Custom layout |
We will use a transformer:
001import { QuartzTransformerPlugin } from "../types"002import { findAndReplace } from "mdast-util-find-and-replace"003import { Text } from "mdast"004005interface Options {006 prefix: string007}008009export const StripPrefixLinks: QuartzTransformerPlugin<Options> = (opts?: Options) => {010 const prefix = opts?.prefix ?? ""011012 return {013 name: "StripPrefixLinks",014 markdownPlugins() {015 return [016 () => {017 return (tree) => {018 const pattern = new RegExp(`\\[\\[${prefix}/([^\\]]+?)\\]\\]`, "g")019020 findAndReplace(tree, [021 [022 pattern,023 (_match: string, captured: string) =>024 ({ type: "text", value: `[${captured}](${captured})` } as Text),025 ],026 ])027 }028 },029 ]030 },031 }032}033
We will place this at .github/Quartz/Quartz/plugins/transformers/stripPrefixLinks.ts. Now, we'll have to register the Plugin at ... /plugins/transformers/index.ts by adding the Line
001export { StripPrefixLinks } from "./stripPrefixLinks.ts"
Now, we enable the plugin in the config .github/Quartz/Quartz.config.ts, by adding it in front of the other transformer plugins:
001//...002plugins: {003 transformers: [004 Plugin.StripPrefixLinks({ prefix: "Blog" }),005 //...006 ]007}, //...
If you want to setup the Blog elsewhere in your Obsidian vault, also modify the prefix here.
To create a custom footer, edit .github/quartz/quartz/components/Footer.tsx. If you just want to add custom links (like to your social media) to the footer, you can change those in .github/quartz/quartz/quartz.layout.tsx.