Thoryn

25 Apr 2026 · Mark Bakker

Why we picked MDX-in-repo for our blog

Headless CMS or MDX files in git? We chose MDX. Here is the reasoning, the trade-offs, and what a post actually looks like end-to-end.

When we sat down to design the blog surface for stg.thoryn.org, we had two realistic options: a headless CMS (Sanity, Contentful, Storyblok) or MDX files committed to the repository. We picked MDX-in-repo. Here is why, and what a post actually looks like end-to-end.

The decision in one sentence

Our authors are developers, our posts ship with the site, and the single hardest constraint is that every post must render a correct OpenGraph preview card in the LinkedIn feed. MDX-in-repo gives us all of that without introducing another external service.

What we ruled out — and why

A headless CMS is the right answer for many sites. When marketing wants to publish without opening a pull request, or when non-technical authors need a rich editor, or when you run thousands of posts a year, a CMS earns its place.

We have none of those pressures. Our authors are the engineering team. Posts are technical. A new post a week would be a lot. Every additional service we add to the stack is another thing to monitor, another bill, another vendor assessment. For a low-volume, developer-authored blog, a CMS is an answer looking for a problem.

What MDX-in-repo gives us

A blog post lives at apps/site/content/blog/<slug>.mdx, and it looks like this:

---
title: Why we picked MDX-in-repo for our blog
description: Headless CMS or MDX files in git? Here is the reasoning.
publishedAt: 2026-04-25
author: Mark Bakker
tags: [engineering, platform, content]
---
 
# Post body starts here, in MDX.

Frontmatter is validated at build time — a post with a missing title or malformed date fails CI before it merges. The body compiles through remark-gfm for tables and task lists, rehype-slug for anchor links on every heading, and rehype-pretty-code for syntax-highlighted code blocks via Shiki. Routes are built with Next.js App Router: /[locale]/blog for the index, /[locale]/blog/[slug] for the detail page.

The OpenGraph problem, solved

LinkedIn is our primary distribution channel, and LinkedIn renders preview cards from og:image. If the image is missing or the wrong size, the post drops into the feed as a bare URL with no preview and loses almost all of its reach.

We didn't want to hand-author a 1200×630 image for every post. Instead, Next.js's ImageResponse API lets us render an OG image from JSX at build time, driven by frontmatter:

import { ImageResponse } from "next/og";
import { getPostBySlug } from "@/lib/blog";
 
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
 
export default async function Image({ params }) {
  const post = await getPostBySlug(params.slug, params.locale);
  return new ImageResponse(
    <div style={{ background: "linear-gradient(135deg, #3b5bdb, #1a2a6e)", /* ... */ }}>
      <div>{post.frontmatter.title}</div>
      <div>{post.frontmatter.author} · {post.frontmatter.publishedAt}</div>
    </div>,
    size,
  );
}

Brand gradient, post title, author, date. Zero design work per post. Every post gets a unique, branded card. The LinkedIn Post Inspector confirms it renders correctly.

Trade-offs we accepted

Marketing can't publish without a pull request. That's fine for us — every post gets a technical review anyway. There is no rich WYSIWYG editor. That's also fine — our authors write in VS Code and we'd rather they think in Markdown than wrestle a block editor. Posts ship on the next deploy, not in real time. For technical content, a few hours is not meaningful delay.

What this looks like in practice

See our Broker product page for the broader context — the blog is one surface of a larger EU-first identity platform, and posts here will mostly be about how that platform is built and why. If you'd like to talk to us about using it, get in touch.

And if you're designing your own blog surface today, the short version is: match the tool to your authors and your volume. For us that's MDX-in-repo. For you it might not be. Pick what will still feel right in two years.