How my personal website is architected and built.

Site Architecture

My site is built with NextJS (with strict TypeScript enabled). It's deployed and served with Vercel.


I use a variety of JavaScript libraries and frameworks:

You can see a list of the dependencies I use in my package.json


  • Sans Serif (Title): Tillsdale (Link to BN Tillsdale)
  • Sans Serif (Body): Public Sans (using @fontsource)
  • Mono: Jet Brains Mono (using @fontsource)
  • Hand Writing: Palmer Lake


All of the content files are notes written in Obsidian flavored Markdown.

Notes are copied from the Obsidian vault directory on my local machine into a private git repo that I use as a git submodule.

The script is in my GitHub repository for and it is run using a script directive from my package.json:

    "cp-notes-and-images": "rm -rf src/content/* && ts-node --esm scripts/get-notes/run.ts"

The content is stored in version control simply to reduce storing and fetching it from somewhere else.

  • All posts are copied into src/content/posts/<FILENAME> and served as /posts/<SLUGIFIED FILENAME>/

When the site builds, the Obsidian paths are flattened out.

As an example, the file for the content you're currently reading lives in the vault path 40 - Projects/ When the content is served, it will be served at posts/

Serving content at the posts root (instead of the full path from Obsidian) makes the URL path durable. I can move it into a different directory and the URL path will persist at the same location it did before.


I use a few scripts to stitch everything together:

  • A script to copy my Obsidian files with status: published into my git repo's content directory

I was originally using Obsidian Publish and serving the site Using and NGINX for Proxying Obsidian Publish but found the solution wasn't meeting my needs.

For durability purposes, I created a Vercel site that uses a redirects function to redirect my paths from to


This is an Obsidian Dataview section that allows me to keep tabs on what's published and if it's missing metadata.

dv.table(["Note", "Description"], dv.pages().filter((p) => {
return p.status === "published" && !p.fileClass
}).map(p => []))


  • 2023-06-14 updated note to match new build and deploy (Astro (JavaScript) and Netlify)
  • 2022-10-05 - added "notes" to the homepage
Share on Twitter