Jaival SaijaDevOps Engineer · AI Automation
About MeProjectsExperienceCVBlogContact Me
  • Email
  • LinkedIn
  • GitHub
  • CV
© 2026.
Jaival Saija.
All Rights Reserved.
← All posts
22 Oct 2026Post2 min read

Building this blog without a database

Why the posts here are MDX files in the repo, how they get from a git push to a static page, and the one Turbopack limit I ran into.

I wanted somewhere to write down what I'm building and learning, so I don't forget it a month later. The first question was where the posts should live.

No database

I write a few posts a month. Nobody can comment or like anything. A database would mean a connection string, a schema, a hosting bill and backups, all for data that changes when I push a commit.

So every post is a file in the repo, content/posts/<slug>.mdx. Publishing is a git push. Vercel builds the site and every post becomes a static page. The history of every post is the git history.

Metadata is a plain export

@next/mdx doesn't read YAML frontmatter, but MDX files can export anything. So each post starts with an object:

export const metadata = {
  title: 'Building this blog without a database',
  date: '2026-10-22',
  summary: 'One line for the feed and the link preview.',
  type: 'post', // or 'update'
};

A zod schema checks that object at build time. If I get a date wrong or forget the summary, the build fails and names the file, instead of shipping a broken page.

Posts and updates in one feed

Long write-ups are post. Short notes are update. The feed shows updates in full and posts as a title and summary, and both get their own page. A draft: true flag keeps a post visible on my machine but out of the production build.

The Turbopack catch

Code blocks are highlighted by Shiki at build time, with a light and a dark theme baked into the HTML. The site's theme class picks one, so switching themes doesn't flash or run any JavaScript.

The catch: Turbopack runs the MDX compiler in Rust, so remark and rehype plugins have to be named by string, and their options have to be plain JSON. A plugin option that is a function won't work there yet. For this blog that was fine. It's worth knowing before you pick a plugin.

2 min left