Site Logo
Site Logo
  • Home
  • Contact
  • Projects
  • Blog
  • The Watering Can
Site Logo
Site Logo

Josh Wood © 2026

My Writing Stack: Next.js + Prisma + Obsidian

How I turned an Obsidian vault into the content layer for a Next.js site, with Prisma quietly handling the database in between. A look at why this combination works and how the pieces fit together.

June 22, 2026
nextjsprismaobsidianweb-devwriting

For a long time I kept bouncing between two failure modes. Either I had a writing tool I loved but no good way to publish from it, or I had a slick publishing setup that I dreaded actually writing in. The stack I've landed on solves both halves of that problem: I write in Obsidian, a thin sync layer pushes that content through Prisma into a database, and Next.js renders the site. Each tool does the one thing it's genuinely good at, and nothing pretends to be something it isn't.

Here's why I settled on it and how the pieces fit together.

The shape of the problem

A personal site has two very different jobs, and they pull in opposite directions.

The first job is authoring. This is the part you do every day, and it has to feel frictionless. You want fast keyboard navigation, links between notes, no chrome, no "are you sure you want to leave the page." If writing feels like filling out a form, you stop doing it.

The second job is publishing. This is the part the reader sees. It needs to be fast, queryable, indexable, and structured — tags, slugs, publish dates, drafts vs. live posts. A pile of loose Markdown files is great for writing and miserable for this.

Most setups force you to pick a side. A pure static-site generator nails publishing but turns authoring into wrestling with frontmatter and rebuilds. A pure CMS nails publishing but makes writing feel like data entry. The trick is to let each layer specialize.

Obsidian: the authoring layer

Obsidian is just Markdown files in a folder on disk. That's the whole pitch, and it's why I trust it. There's no proprietary format, no lock-in, no server that can go down and take my drafts with it. If Obsidian disappeared tomorrow, I'd still have a directory full of .md files I can read with anything.

What it gives me on top of plain files is the stuff that actually makes writing pleasant:

  • Backlinks and graph view — I can connect a post to the half-formed notes that fed it.
  • Instant search across everything I've ever written.
  • Frontmatter for the structured bits a post needs:
---
title: My Writing Stack: Next.js + Prisma + Obsidian
slug: nextjs-prisma-obsidian-stack
tags: [nextjs, prisma, obsidian]
published: false
excerpt: How I turned an Obsidian vault into the content layer for a Next.js site.
---

For a long time I kept bouncing between two failure modes...

The published: false flag matters: a post lives in the same vault whether it's a rough draft or ready to ship. I don't move files between "drafts" and "published" folders. I flip a boolean.

Prisma: the boring, load-bearing middle

This is the part people are surprised by. Why put a database — and an ORM — between a folder of Markdown and a website?

Because the moment your site has more than a handful of posts, you start wanting queries. Give me the five most recent published posts. Give me everything tagged nextjs, sorted by date. Give me the post with this exact slug, or a clean 404. Doing that against the filesystem on every request means reading and parsing the whole vault constantly. Doing it against a database is a single indexed lookup.

Prisma is what makes that database pleasant to work with from TypeScript. The schema is the source of truth, and it reads like a description of the thing it models:

model Post {
  id          String    @id @default(auto()) @map("_id") @db.ObjectId
  slug        String    @unique
  title       String
  body        String
  excerpt     String?
  tags        String[]
  published   Boolean   @default(false)
  publishedAt DateTime?
  updatedAt   DateTime  @updatedAt
}

From that one block, Prisma generates a fully typed client. When I query a post, my editor knows published is a boolean and publishedAt might be null, and it yells at me before runtime if I get it wrong. That type safety flowing all the way from the database into my React components is the thing I'd miss most if I switched away.

The sync itself is a small script — it walks the vault, parses frontmatter, and upserts each post by slug:

for (const file of vaultMarkdownFiles) {
  const { data, content } = matter(file.raw)
  await prisma.post.upsert({
    where: { slug: data.slug },
    update: { title: data.title, body: content, tags: data.tags, published: data.published },
    create: { slug: data.slug, title: data.title, body: content, tags: data.tags, published: data.published },
  })
}

Upsert is the key choice. Editing a post in Obsidian and re-running sync updates the existing row instead of creating a duplicate, because the slug is the stable identity. The filesystem stays the thing I edit; the database stays the thing I query.

Next.js: the rendering layer

Next.js ties it together and serves the result. With the App Router, each post is a server component that queries Prisma directly — no separate API layer to maintain for my own reads:

// app/blog/[slug]/page.tsx
export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await prisma.post.findUnique({
    where: { slug: params.slug, published: true },
  })
  if (!post) notFound()
  return <Article post={post} />
}

Because the data lives in a database rather than the filesystem, I get to choose per page how fresh it needs to be. The blog index can be statically generated and revalidated on an interval. An individual post can render on demand. I'm not rebuilding the entire site every time I fix a typo — I re-run sync, and the affected pages pick up the change.

Why this split is worth it

You could absolutely build a simpler version of this. Plenty of people read Markdown straight off disk at build time and never touch a database, and for a small enough site that's the right call. What the extra Prisma layer buys me is room to grow without re-architecting:

  • Authoring and publishing are decoupled. I can completely change how the site looks without touching how I write, and vice versa.
  • The content is portable. It's plain Markdown in a folder. The database is a derived cache I can blow away and rebuild from the vault at any time.
  • Everything downstream of the schema is typed. Prisma's generated types catch a whole category of bugs before they ship.
  • Queries stay cheap. Tags, search, related posts, pagination — all the features I'll want later are just database queries, not filesystem gymnastics.

The throughline is that each tool keeps its own job. Obsidian never has to care that the writing becomes a website. Next.js never has to care where the words came from. And Prisma sits quietly in the middle, turning a folder of notes into something a website can actually ask questions of.

That separation is the whole point. The best stacks aren't the ones with the fewest moving parts — they're the ones where every part has an obvious reason to exist.

JW

Written by Josh Wood

Full-stack developer based in Niagara, Ontario, specializing in TypeScript, React, and Next.js — building software that holds up in the real world, front end to infrastructure.

github.com/Kalaghni