...
Back

Content as Code Means Your Content Has a Build Step

Keeping docs and posts in the repo buys you review, history, and types. It also makes writing a paragraph something that can fail CI — and that tradeoff is the whole decision.

Content as Code Means Your Content Has a Build Step

Content as Code Means Your Content Has a Build Step 📦

Our documentation and blog live in the repository as MDX, compiled into typed objects at build time. It is a good setup and I would choose it again. It is also a setup whose costs are invisible on the day you adopt it and obvious six months later, so here is the honest ledger.


What you get, concretely

Types over content. The generated layer means a page that lists posts knows a post has a date and a lang. Rename a frontmatter field and the build tells you every place that used it. With a hosted CMS, that rename is a runtime surprise in whichever template you forgot.

Review that works. A copy change arrives as a diff. Someone can comment on a sentence. The change ships with the code change it describes, in the same commit, or it does not ship. For a product where the docs describe behavior that is changing, this single property is worth most of the cost.

One history. git log on a content file answers "when did we start claiming this, and what else changed that day". That question comes up more than you would expect, usually when something in the docs turns out to be wrong.

No runtime dependency. The content is in the bundle. There is no API to be down, no rate limit, no separate cache to invalidate, no auth token to rotate.


What it costs

A paragraph can now break the build. Unclosed JSX in an MDX file, a frontmatter field missing from one translation, a date in the wrong format — these are content mistakes with code consequences. The person best placed to write the paragraph is often least equipped to read the resulting error.

Ordering becomes load-bearing. The generated content layer must exist before typechecking, before linting, before the app build. Get the order wrong in CI and you get errors that point at the app and originate in the pipeline. Our build script is contentlayer build && next build for exactly this reason, and every environment that runs one of those steps in isolation has to know it.

Generated artifacts are a category of confusion. They are in .gitignore, so a fresh checkout has none of them, so a typecheck before a content build fails with hundreds of missing-module errors that look catastrophic and mean nothing. Every new contributor hits this once.

Your content pipeline pins your toolchain. MDX compilers sit on top of bundlers, and bundlers move. We have had to pin a transitive build dependency to keep the content step working through a framework upgrade. That is a real maintenance surface that a CMS simply does not have.


The rules that make it hold up

Four practices that turned this from a recurring annoyance into a non-issue:

  1. Validate frontmatter with a schema, strictly. Every field, every required flag. A schema error naming the file and the field is a message a writer can act on. A downstream undefined is not.
  2. Make the content build a prerequisite of everything else, in one place. One script that runs content-then-app, referenced by CI, by the container build, and by the local command. Not three copies that can drift.
  3. Check pairing and completeness in CI. For us, posts come in language pairs — so a test asserts that every post has its counterpart, and that no locale is missing a field the others have. This catches the "shipped English-only by accident" case before review, not after.
  4. Keep a one-command local preview. If a writer cannot see their page without understanding the pipeline, they will stop writing, and the docs will rot in a way no tooling fixes.

When to choose the other thing

Content as code is the wrong call when the people writing outnumber the people building, when content changes need to go live without a deploy, or when publishing is scheduled and non-technical. A marketing team that ships twenty pages a week should not be blocked on CI, and telling them to learn frontmatter is not a strategy.

The clean test is: does a content change need to be reviewed alongside a code change? If yes — docs for a product that is actively changing, posts that make technical claims — put it in the repo and accept the build step. If no, the build step is pure friction, and you are making writers pay a tax that buys them nothing.