HomeBlogEngineering
Engineering

Vibe Coding a CMS

How we built MoneyOnFIRE's content system with AI-assisted development — and then leveraged AI within tight guardrails to assist with content

February 28, 2026
10 min read
Vibe Coding a CMS

The Problem

MoneyOnFIRE is a financial planning engine. It runs month-by-month simulations of your financial life, handles tax calculations across account types, models RSU vesting, and produces a personalized roadmap to financial independence. The engine is the product.

But an engine alone isn't enough. The concepts behind the calculator — safe withdrawal rates, Roth conversion ladders, sequence of returns risk — need explanation. We needed educational content that could sit alongside the tool and help people understand what the numbers mean and why they matter.

Key requirements:

  • Interactivity: Articles needed to embed our own interactive components — tax breakdowns, savings rate charts, vesting schedule visualizations. Static prose wasn't going to cut it.
  • Consistency: Every article needed to look and feel like part of the same product, not a collection of one-off blog posts.
  • Simplicity: We're a small team. Writing and maintaining dozens of articles by hand doesn't scale.

This article describes how we solved all three — first by building the content infrastructure, then by designing an editorial system for AI-assisted writing. It was produced by the system it describes.

What We Evaluated

Before building anything, we looked at the established options.

Markdown / MDX

Tools like MDX and Contentlayer let you write markdown with embedded React components. Good for prose-heavy content.

  • Simple authoring for text-heavy articles
  • Custom layouts required awkward workarounds
  • Styling inconsistency across articles — each file could drift
  • Embedding interactive components felt like fighting the tool

Headless CMS

Contentful, Sanity, Strapi — purpose-built content platforms with APIs, dashboards, and content modeling.

  • Built for content management at scale
  • Added infrastructure, API calls, and deployment complexity
  • Content modeling overhead for what is fundamentally a blog with charts
  • Another system to learn, configure, and maintain

These are good tools, and the right choice for many teams. For us, they were solving problems we didn't have while not solving the one we did: embedding our own interactive React components with full Tailwind styling, in a way that felt like writing code rather than fighting a content platform.

What We Built Instead

Every article is a React component. The only contract is that it exports a metadata object.

That's the entire idea. An article is a .tsx file that exports two things: a default React component (the content) and a typed metadata object (title, category, slug, keywords). There is no database, no CMS dashboard, no content API. The “CMS” is a folder.

Because articles are React components, they have the full power of the framework. They can import interactive chart components, use Tailwind for pixel-level layout control, and embed calculators directly. The RSU article, for example, imports four custom components — a lifecycle diagram, a vesting schedule chart, a tax breakdown calculator, and a sell-vs-hold comparison. No iframe, no embed code, no CMS plugin — just import.

What an article looks like

Every article is a single file. It exports metadata (for routing and display) and a React component (the content):

import { ArticleMetadata } from '@/types/article';
import Link from 'next/link';
import SavingsRateChart from '@/articles/components/SavingsRateChart';

export const metadata: ArticleMetadata = {
  slug: "compound-interest",
  title: "The Power of Compound Interest",
  category: "Maths and Mechanics",
  image: "/resources/compound/hero.jpg",
  priority: 2,
  readTime: 6,
  date: "2025-01-15",
  keywords: ['compound interest', 'savings rate', 'FI timeline'],
};

export default function ArticleCompoundInterest() {
  return (
    <article className="max-w-4xl mx-auto space-y-16">
      <section className="space-y-6">
        <h2 className="...">How Savings Rate Moves Your FI Date</h2>
        <p className="...">...</p>

        {/* Interactive component — imported directly */}
        <SavingsRateChart />
      </section>

      {/* ...more sections... */}
    </article>
  );
}

A 138-line loader file does the rest. It uses webpack's require.context to auto-discover every .tsx file in the articles directory, filter out drafts, and build the article index. Categories, sort order, and prev/next navigation are all derived from metadata — no routing config to maintain.

The entire “CMS”

function importAll(r: RequireContext) {
  const articles = {};

  r.keys().forEach((fileName) => {
    const module = r(fileName);

    if (module.default && module.metadata && !module.metadata.draft) {
      articles[module.metadata.slug] = {
        component: module.default,
        metadata: module.metadata
      };
    }
  });

  return articles;
}

export const articles = importAll(
  require.context('./', true, /\.tsx$/)
);

To publish an article, you write a TSX file and push it. To unpublish, you add draft: true to the metadata. Articles are just code — version controlled, reviewable, and deployable like everything else.

The Content Quality Problem

The infrastructure was solved. We had a simple, flexible content system that did exactly what we needed. The next question was harder: how do you produce 20+ articles and keep them consistent?

We use Claude Code as our development environment. It was natural to use it for content too. But early attempts revealed two problems:

Problem 1: Accuracy

LLMs write fluently but get financial details wrong. A paragraph about Roth conversion ladders might sound authoritative while containing a subtle error about income thresholds. Without guardrails, we spent more time fact-checking and rewriting than we saved by having AI draft the content in the first place.

Problem 2: Consistency

Each article came out with slightly different structure, tone, heading styles, and callout patterns. One article used blue info boxes, another used yellow. One used h3 for section headings, another used h2. It looked like five different authors had each written one piece. For a product that values a unified experience, this wasn't acceptable.

These are editing problems, not writing problems. The AI is a fast but unsupervised junior writer. It needs an editor-in-chief.

The Editorial Control System

Our answer to both problems is three markdown files and a simple process. Here's the overview:

The content manufacturing process

The brief is the centrepiece. The style guide and CLAUDE.md exist to support it. Here's how each file works:

.brief.md — the assignment sheet

Each article has a companion .brief.md file alongside the .tsx. This is where the human author writes what the article needs to say: key points, the arc of the argument, framing, things to avoid, target audience. The brief takes priority over AI judgement on content direction.

This is the most important file in the system. Without it, the AI produces generic, plausible-sounding content. With it, the AI produces your content — making the specific points you want to make, in the order you want to make them, while avoiding the angles you've ruled out.

Here is an excerpt from this article's own brief:

## Article arc

### 1. The problem
- We needed content alongside the engine
- Content had to embed our own interactive components
- We're a small team; writing dozens of articles
  by hand doesn't scale

### 2. What we evaluated and why it didn't fit
- Wiki/markdown (MDX, Contentlayer): inflexible for
  custom layouts, styling inconsistency across articles
- Headless CMS (Contentful, Sanity, Strapi): too heavy
  for what is fundamentally a blog with interactive charts
- Our requirements: quick to author, flexible layout,
  embedded components, consistent styling, fast builds

### 3. What we built instead
- TSX files in a folder — each article is a React
  component with exported metadata
- require.context loader auto-discovers articles,
  filters drafts, builds navigation
- Articles are just code — version controlled,
  reviewable, deployable like everything else

The brief files are checked into git but never built, bundled, or deployed. The article loader only matches .tsx files, so the briefs are invisible to the production site. They persist across AI editing sessions — you can revise an article months later and the brief is still there, still guiding the output.

STYLE_GUIDE.md — the editorial standards

If the brief controls what an article says, the style guide controls how it says it. It codifies tone of voice, heading hierarchy, Tailwind class names for callout boxes, the CTA section at the end of every article, and the exact JSX patterns for comparison cards, tables, and character profiles.

A few examples:

## Voice & Tone

### Do
- Professional and factual — write like a knowledgeable peer
- Respectful of other options and prior work in the
  FI community (4%, Spreadsheets, etc)
- Concrete — use real numbers, worked examples, and
  specific scenarios

### Don't
- No emojis anywhere in articles
- No informal exclamations
- No denigrating other tools, approaches, or communities
- No first-person singular — use "we" or address "you"
- Never describe a MoneyOnFIRE feature that doesn't
  exist in the codebase — verify against engine source

The style guide also includes the exact TSX snippets for visual elements. This means the AI doesn't invent new patterns for each article; it uses the established ones. The consistency problem largely goes away because every article draws from the same palette.

CLAUDE.md — the automatic trigger

The final piece ties it together. CLAUDE.md is the file that Claude Code reads at the start of every session. A short section tells it to load the style guide and the article's brief before doing any work:

## Article Authoring

When creating, editing, or reviewing any article
in articles/, always read these files first:

1. articles/STYLE_GUIDE.md
2. The article's .brief.md (if one exists)

No manual step to remember. The editorial context is loaded automatically, every time.

When Content Understands Code

There's an unexpected advantage to using an AI development environment for content: it already understands the product.

Claude Code can read our entire calculation engine codebase. When we ask it to explain how the account funding waterfall works, or describe what the Monte Carlo simulation does, it writes more accurately because it has read the implementation. Not because we described the feature in a prompt — because it has seen the actual tax calculation code, the withdrawal ordering logic, the RSU vesting model.

No translation layer

In a traditional content workflow, an engineer explains a feature to a writer, the writer interprets it, and the engineer reviews the result for accuracy. Information degrades at each handoff. In our system, the same AI that helped build the feature writes the explanation. The RSU article's tax breakdown calculations, for example, are grounded in the actual tax computation code — not a second-hand description of it.

This doesn't eliminate the need for human review. The AI can still phrase things misleadingly or emphasize the wrong aspect. But the baseline accuracy is higher because the source of truth is the code itself, not someone's memory of how the code works.

What “Vibe Coding” Means Here

The term “vibe coding” gets used loosely. Some people mean “describe what I want and let the AI write everything.” That's not what we mean.

For us, vibe coding is an iterative conversation where the human steers direction and the AI builds. You describe the shape of what you want, the AI produces a first pass, you review and adjust, and the cycle repeats. It's closer to pair programming with a very fast junior developer than it is to handing off a spec.

The CMS infrastructure was vibe coded. We described the requirements — auto-discovery, metadata-driven navigation, draft filtering — and iterated until it worked. The total amount of code is small enough that we understand every line.

The infrastructure is vibe coded. The content is not — it's structured, reviewed, and author-directed.

The editorial system exists precisely because vibe coding alone isn't enough for content. Disposable infrastructure can be rebuilt if it's wrong. An inaccurate article about Roth conversion tax implications cannot. The style guide, briefs, and automatic loading instructions are what turn a fast-but-unreliable drafter into a useful authoring tool.

The Total System

The total infrastructure is: a type definition, a 40-line loader function, a style guide, and a one-paragraph instruction in the project configuration. No CMS dashboard, no content API, no deployment pipeline for content. Articles are code, and they ship like code.

It's not perfect. The style guide doesn't catch every inconsistency. The briefs don't prevent every wrong emphasis. Some articles still need more revision than others, and the system is still evolving as we learn what works. But it's a meaningful improvement over either writing everything by hand or letting the AI draft without guardrails.

It works for us because we're a small team producing educational content for a specific product. If we needed non-technical editors, multi-language support, or a content approval workflow with role-based permissions, we'd need a real CMS. But we don't, so we don't.

Ready to see your path?

Use our planner to calculate your FI number and get a personalized roadmap to financial independence.

Build My Plan

Ready to Optimize Your Path to Financial Independence?

Use MoneyOnFIRE's calculator to see exactly how different strategies affect your timeline to FI.