./fru-mark/blog/markdown-as-a-content-api

← All blogs

Markdown as a content API

A CMS is a lot of moving parts for a site with twelve pages. I keep content in markdown files and expose it through route handlers instead.

Mark Carrington
Software Engineer2 min read
  • Next.js
  • Architecture
  • Markdown

This site has no database. Posts are .md files in a blogs/ directory, projects are .md files in a projects/ directory, and both are served over HTTP by route handlers that read the file system at request time.

The shape of the pipeline

There are three layers and each one has a single job.

The content layer reads files and parses frontmatter. The route handlers turn the parsed result into JSON. The pages fetch that JSON. Nothing in the UI knows that markdown exists, which means swapping in a real CMS later is a change to one file.

Splitting a post into sections

A table of contents needs to know the headings before the body renders, so the parser walks the markdown line by line and starts a new section at every ##. Everything before the first heading becomes the intro.

Two details matter more than they look like they should:

  • Headings inside fenced code blocks are not headings. The parser tracks whether it is inside a fence and skips matches while it is.
  • The anchor id has to be derived the same way every time, or deep links break silently the first time a title gains a comma.

Frontmatter as a schema

Frontmatter is untyped by nature, so the parser coerces every field and supplies a default. A post missing a date sorts last instead of crashing the list page, and a project missing its architecture table renders without the table rather than with an empty one.

tags: Array.isArray(data.tags) ? data.tags.map(String) : []

It is defensive code, but the alternative is that a typo in a YAML block takes down the whole route.

Where this stops working

Around a few hundred documents, reading every file to build a list page becomes the slow part, and that is the point to add a build-time index or move to a real database. Below that, the file system is faster than a network round trip to a CMS and considerably easier to reason about.