byte5
← All posts
BLOG

How we built the byte5 blog

·2 min read
MDXNext.jsEngineeringbyte5
Christian Wendler
Managing Director

We wanted a blog that feels like the rest of byte5.ai: fast, static, no database, no vendor lock-in. No headless CMS, no external API — content lives as files in the repository and goes through the same review process as code.

This post walks through the architecture behind it and why it scales well for a multilingual team.

Content as files#

Every post is one MDX file per language plus a typed entry in the registry. The registry holds all language-independent fields — date, tags, author, cover:

export const posts: BlogPostMeta[] = [
  {
    slug: "wie-wir-den-byte5-blog-gebaut-haben",
    publishedAt: "2026-05-20",
    tags: ["MDX", "Next.js", "Engineering", "byte5"],
    authorId: "christian-wendler",
    sourceLanguage: "de",
  },
];

The nice part: filtering and sorting on the overview run purely on this typed data. The actual prose stays where it belongs — in the MDX file.

Content belongs in the repository, not in a database. That way every blog post gets the same care as any code change: branch, review, diff.

From markdown to a server component#

The MDX files are compiled at build/render time and rendered as a React Server Component. Syntax highlighting is handled by Shiki — entirely on the server, without shipping a single kilobyte of JavaScript to the browser:

The MDX pipeline: from file through compilation to a server component

Why no client library?#

Highlighting, heading anchors and GitHub-flavored markdown all happen at render time. The browser receives finished HTML. That keeps pages light and Core Web Vitals green.

Prepare, don't rush#

One detail mattered to us: posts with a publish date in the future do not appear in the overview — yet the detail page is already reachable by link. So posts can be prepared calmly and go live automatically once their date arrives.

export const revalidate = 600;
 
export default async function BlogIndex() {
  const now = Date.now(); // always evaluate at render time
  const published = getPublishedPosts(now);
  // ...
}

Multilingual support rounds it out: a post is written in German or English and translated into the remaining languages. If a translation is missing, the page falls back cleanly to the source language and shows a subtle language badge.

The result is a blog that is technically boring — in the best sense. That is exactly what we wanted.