·6 min read

Why blog posts belong in your monorepo, not a SaaS dashboard

Content is code-adjacent. It ships with releases, needs review, and benefits from version history. Here's why the dashboard is the wrong surface for AI writing tools, and what changes when drafts arrive as pull requests instead.

BA
Beka A.
Founder
/content *.mdx
monorepo/blog

Every AI writing tool I evaluated before building Branchpost stored drafts in a dashboard. You log in, you see a list of posts, you click one, you edit it in a rich text box, you hit publish. The content lives on their servers. Your repo never sees it until you copy-paste the output into a file, or a webhook deposits a half-broken markdown blob into a directory nobody owns. That entire arrangement is wrong, and it's wrong for the same reason that storing your application config in a Notion page is wrong: the content is part of the product, and the product lives in the repo.

This post is the argument for why blog posts belong next to your code, not in someone else's CMS, and why a pull request is the correct delivery surface for an AI-generated draft.

Content ships with releases

A blog post that announces a feature is part of that feature's release. If the post goes live before the deploy, the links 404. If the deploy goes out before the post, the launch is silent. The teams I've watched solve this problem cleanly all do the same thing: they put the post in the same PR as the feature, or in a PR that's gated on the feature's merge. The post and the code move together because they describe the same change.

This is impossible when your drafts live in a SaaS dashboard. The dashboard has no concept of your release branch. It doesn't know your feature is behind a flag. It can't be blocked on a CI check. It exists in a parallel timeline that you have to manually reconcile with your actual ship calendar, and that reconciliation is where launch announcements break.

When the draft is a file in the repo, it inherits the entire release machinery for free. Branch protection. Required reviewers. Status checks. Preview deploys. None of that has to be reinvented for content because content is just another set of files in the tree.

Content needs review, and you already have a review tool

Dashboards always grow their own review workflow eventually. They add comments. They add suggested edits. They add approval states. They reinvent, badly, a system that already exists and that your team already uses every day: pull requests.

This is the core bet behind making every draft a pull request. The PR isn't a metaphor. It's the literal mechanism. A Branchpost draft lands as a real branch with a real diff, and reviewers leave line comments on prose the same way they leave line comments on TypeScript. The muscle memory transfers. The tooling transfers. The notifications transfer. There is no second inbox to check.

The side effect that surprised me most: the tone of review changes when prose is in a PR. People are more willing to suggest a concrete rewrite on line 47 than to write a paragraph of feedback in a dashboard comment box. The diff invites specificity.

Version history is not optional for published claims

A blog post is a public claim with your name on it. Six months from now, someone is going to ask why you wrote that the cache hit rate was 73%, and you are going to want to know exactly when that number was written, who approved it, and what the commit message said. Git gives you this for free. Dashboards mostly do not, and the ones that do give you a worse version of it locked behind an export button.

The same applies to corrections. When you update a post, the diff is the audit trail. You can see what changed, why, and when. If your CMS is a SaaS textarea, your audit trail is "last edited 3 months ago by someone."

The contract between AI and CMS only works in-repo

There's a deeper technical reason content belongs in the repo, and it shows up the moment you try to make AI generation reliable. The model needs to produce output that matches your site's exact schema: the frontmatter fields, the tag vocabulary, the MDX components you've defined, the link conventions you use. That schema lives in your codebase. The example posts that teach the model your voice live in your codebase. The style guide lives in your codebase.

A dashboard tool has to either ask you to re-upload all of that, or it has to guess. Both options are worse than the obvious alternative: read the repo. Frontmatter as a contract only works as a contract when both parties can see the schema, and the cleanest way for the model to see it is for the model's output to land in the same tree.

What the dashboard-first tools actually optimize for

Be honest about the incentive. SaaS dashboards optimize for lock-in. If your drafts live on their servers, your switching cost is high. If your drafts live in your repo as plain MDX files, you can uninstall the tool tomorrow and lose nothing. That asymmetry explains most of the product decisions in the category. It's not that dashboard-first companies haven't thought of PRs. It's that PRs make the tool replaceable, and replaceable tools don't have great retention curves.

Branchpost is replaceable on purpose. The files it writes are yours, in your repo, in a format your site already knows how to render. If we shut down tomorrow, your blog still builds. That's the test for whether a content tool respects the fact that content is code-adjacent: can you uninstall it without losing anything but the generation step?

The takeaway

If your blog is a real part of your product, the drafts should live where the rest of the product lives. The review should happen where the rest of the reviews happen. The history should be the same history. Anything else is a parallel system that you will eventually have to reconcile by hand, and the reconciliation is where quality dies.

Branchpost watches your repo and opens PRs with blog drafts. You review them like code. branchpost.dev

posts/blog-posts-belong-in-monorepo.mdx