From Obsidian to Next.js: how this site is built from a second brain

4 min readApril 29, 2026#next.js#cms#markdown#brain#pipeline#remark

How I built this site — the Brain → site pipeline

This site has no back office. No database. No external CMS.

The CMS is my Second Brain — an Obsidian vault in ~/Brain/.


The principle

Every article you read here started life as a Markdown note in my digital brain.

The pipeline:

~/Brain/projets/portfolio/drafts/  →  content/<section>/  →  git push  →  Vercel

Copy a file, commit, push. The post goes live in 60 seconds.


Why not a real CMS

Sanity, Contentful, the Notion API, Strapi — I evaluated all of them.

The real constraint: I already produce content from Brain anyway. My notes are already in Markdown, already structured, already cross-linked. An external CMS adds friction for zero gain.

A .md file on the filesystem is:

  • Versionable with git
  • Openable in any editor
  • Readable offline
  • Free

The problem: inside Brain, links between notes get written naturally.

See [Rule F-01](./f01-regle-balance.md) for the full pattern.

On the site, that link needs to become /insights/f01-regle-balance. Not ./f01-regle-balance.md.

I built a remark plugin (remarkResolveLinks) that:

  1. Builds a manifest of every post at build time: slug → Post
  2. Intercepts link nodes with relative .md paths
  3. Replaces the URL with the correct Next.js route
visit(tree, "link", (node: Link) => {
  if (!/^\.\.?\//i.test(node.url)) return;
  const filename = node.url.split("/").pop() ?? "";
  const post = manifest.get(filename);
  if (post) node.url = post.url;
});

Result: I write cross-links in Brain exactly the way I'd write them in Obsidian. They just work on the site, with zero modification.


The slug

Brain files carry a date prefix: 20260429-1100-article-moneroo-sdk.md.

I don't want that date in the URL. /articles/20260429-1100-article-moneroo-sdk is unreadable.

toSlug() strips the prefix:

function toSlug(filename: string): string {
  return filename
    .replace(/\.(md|mdx)$/, "")
    .replace(/^\d{8}-\d{4}-/, "");
}

Final URL: /articles/article-moneroo-sdk. Clean.


The frontmatter

Every .md file starts with a YAML block.

---
title: "XOF n'a pas de centimes — le bug qui m'a coûté 3h"
date: 2026-04-29
topic: paiements
order: 1
tags: [moneroo, xof, devises, bug]
status: published
related:
  - f01-regle-balance
---

topic and order group insights into sections on the /insights page. related gets resolved into full Post objects — rendered as a "See also" section beneath the article. status: archived pulls a post from the listings without deleting the URL.


The sorting bug

Gray-matter parses date: 2026-04-29 into a Date object, not a string.

My first sort used .localeCompare(). It doesn't blow up right away — Date.localeCompare returns undefined, which passes silently in dev and quietly scrambles post order in production.

The correct version:

const da = a.frontmatter.date ? new Date(a.frontmatter.date).getTime() : 0;
const db = b.frontmatter.date ? new Date(b.frontmatter.date).getTime() : 0;
return db - da;

Always convert to a timestamp before comparing YAML dates.


The design

Inspired by nav.al/archive. Black. White. Nothing else.

Full palette:

  • Background: #090909
  • Text: #f0f0f0
  • Meta: #888888
  • Separators: #333333

Typeface: Jost 300/400/500. Zero icons. Zero decorative images.

The design constraint: if you strip away everything that isn't text, is the information still readable? On this site, the answer is yes.


The full workflow

  1. Write the draft in ~/Brain/projets/portfolio/drafts/
  2. Add the frontmatter (title, date, status, tags, related)
  3. Copy into content/<section>/
  4. git add content/ && git commit -m "contenu: ..." && git push

That's it.

Vercel picks up the push, rebuilds the site via SSG, and deploys. Every page is statically generated from the Markdown files through generateStaticParams.


The real value here isn't the site. It's that the pipeline costs zero friction.

If publishing is hard, you don't publish. If publishing is easy, you publish more often.