· Ben · process · 6 min

Review content the way you review code

Your content lives in a git repo, but the review ritual didn't come with it. Pull request size limits, a response-time rule, CODEOWNERS on content/**, prose linting in CI, and the three things a linter will never catch.

The diff is 40 lines, because every paragraph is one long line. Nobody is assigned. It sits from Tuesday to Friday, collects two typo comments and an LGTM, and merges.

That's what content review looks like at most teams that have moved their blog into a repo. The pipeline works. Markdown in, static site out, deploy preview on every push. What didn't survive the move is the ritual: the thing engineers do to every change, automatically, without anyone scheduling a meeting about it.

Search for a content-as-code workflow with git and markdown and you'll find a dozen good posts about storing and publishing. Repo layout, frontmatter conventions, CI that builds the site. Almost none of them say a word about approval. That's backwards. The generator was never the hard part. The review loop is where quality actually comes from, and code review has spent twenty years figuring out how to run one. Here's the port.

A 1,800-word draft is a 400-line change

SmartBear's study of a Cisco Systems team is the number everybody in code review quotes: ten months, roughly 2,500 reviews, 3.2 million lines of code. The conclusion is that you should review 200 to 400 lines at a time, over 60 to 90 minutes, and that a review in that range finds 70 to 90% of the defects present. Past 400 lines, defect detection collapses. The reviewer is still reading. They've just stopped seeing.

An article is one reviewable unit. A 1,800-word post runs maybe 130 sentences and 40 minutes of honest attention, the same shape as a mid-sized pull request. Ten articles in one branch is a 4,000-line refactor, and it gets the same review a 4,000-line refactor gets, which is none.

This is the entire reason Contentcron opens one pull request per article on a contentcron/<slug> branch instead of batching a week's output. One unit in, one decision out. The PR body says what the article covers and why the topic was picked, so the reviewer starts with context instead of reverse-engineering it from the H1.

One business day, or you're reviewing a stale draft

Google's public engineering practices state the rule flatly: one business day is the maximum time to respond to a review request. Not to finish the review, not to bless it. To respond. A first round of comments beats a complete review that lands next week.

Content teams break this worse than engineering teams do, because there's no build queue making the delay visible. A draft waits four days, then comes back with three paragraphs of structural rewrite. By then it has lost its news hook, its competitor reference, and the author's memory of why they wrote it that way. The cost isn't the four days. It's that the second pass now starts from cold.

Put a number on it and treat missing it as a bug. If the draft can wait a week, it wasn't a topic worth researching.

Write markdown for the diff, not for the word processor

One sentence per line. That's the whole technique.

Write a paragraph as one long line and changing four words repaints the entire block red and green. The reviewer has to diff it in their head. Break on sentences and a reworded clause shows up as one line changed, with the rest of the paragraph sitting untouched around it. Frontmatter behaves the same way: description and tags on their own lines diff cleanly, and you can see at a glance that someone quietly changed the slug.

Honest limit: GitHub still won't render markdown previews inside pull request comments. It's an open community request, it's been open a while, and until it lands you are reading raw MDX in the review pane. That's fine for structure and voice. It's useless for layout.

The preview link is the other half of the review

Netlify has built a unique preview URL for every pull request since 2016, prefixed deploy-preview-<PR number> and surfaced as a commit status on the PR itself. Vercel, Cloudflare Pages and Netlify all do a version of this now. It's old, boring infrastructure that has worked for years, and half the content teams I talk to still don't use it as a review artifact.

Review both surfaces or you'll miss half the defects. The diff catches voice drift, a hedged claim, a stat with no citation, a heading that promises something the section doesn't deliver. The rendered page catches the 400-word wall with no subhead, the code block that overflows on mobile, the figure that renders three shades off your palette, the internal link that 404s.

Read the diff for what the article says. Read the preview for what it feels like to land on.

A content-as-code workflow needs an owner on content/**

If nobody's name is in CODEOWNERS, nobody owns the voice.

The mechanism is unglamorous and takes ten minutes. Add a CODEOWNERS entry mapping content/** or posts/** to a person or a team, then turn on Require review from Code Owners in branch protection. With multiple owners listed, approval from any one of them satisfies the rule, so this doesn't become a single-person bottleneck unless you write it that way.

GitHub made the path-scoped version generally available on February 17, 2026, as the required reviewer rule in rulesets. Their example is *.sql always getting reviewed by the data platform team. The port is direct: content/** always gets reviewed by whoever owns how the company sounds. It sits on top of CODEOWNERS rather than replacing it, so you can run both.

The point of the file isn't enforcement. It's that the question "who signs off on this" now has an answer written down, and the answer isn't "whoever notices."

A linter catches passive voice, not a made-up number

Prose linting in CI is real and you should run it. Vale is a syntax-aware linter written in Go with an official GitHub Action, and it will enforce the Microsoft Writing Style Guide or the Google developer documentation style guide out of the box. Elastic runs it against every commit in a docs pull request; their packaged rules apply to Markdown and MDX by default, with per-repo severity overrides in a .vale-overrides.ini. Banned words, heading capitalisation, sentence length, "utilize." All of it, automatically, before a human opens the tab.

Now the ceiling. A style linter is syntax-aware, not truth-aware. It cannot tell you that a statistic was invented, that a chart's axis is decoration, or that the third paragraph sounds like a different company than the first. Those are the three failures that actually damage a blog, and every one of them survives a green CI check.

So the gate has to sit on sourcing. Style rules can't reach that far. Ours is blunt: a figure that can't cite a source is rejected. Not flagged, rejected. Charts print their source inside the image. There's no AI image generation in the pipeline, by design, because a generated illustration is a claim nobody can check. The longer argument is in our first eight weeks of Search Console data: 318 clicks and 43,627 impressions on a blog where every number in every post traces back to something real. Sourcing did more for that than tone ever did.

Comments in, commits out

GitHub's suggested changes let a reviewer write the exact replacement text on a line, and let the author commit a batch of them as a single commit. That's the right shape for a typo. It's the wrong shape for "this section argues the point twice and buries the mechanism."

Contentcron inverts it. You write the intent as a line-level comment, in whatever words you'd use in Slack, and a revision commit lands on the same contentcron/<slug> branch in about a minute. Leave five comments across the article and they get batched into one coherent revision rather than five patches that fight each other, with a reply on the PR summarising what changed. Prefix a comment with !cc-ignore and it's excluded, which matters when two people are arguing in the thread and only one of them is giving instructions.

A finished MDX article alongside its pull request link, research notes and activity tabs, with the in-app revision chat open.

This lives in the PR and not in a chat window because the PR is where the rest of the team already is. Review threads, approvals, and the merge all sit in one place, and the audit trail is the git history. That's most of what the whole product is: the review surface is GitHub, not a dashboard you have to remember to open.

What this doesn't fix

It doesn't fix a reviewer who won't read. CODEOWNERS assigns the name; it can't make the person open the tab. If your one-business-day rule is missed every week, that's an ownership problem and no ruleset will patch it.

It doesn't work if your content lives in a database-backed CMS. Contentful, Sanity, Strapi, WordPress: there's no file, so there's no diff, so there's no PR to review. We're not there yet, and I'd rather say that than pretend.

And it doesn't make review optional. There's no setting that skips the human. If what you wanted was a merge button, you wanted a different tool.

If your blog is a folder of markdown, your deploy previews already work, and the part nobody owns is the twenty minutes between draft and merge, this is the ritual worth stealing. Try it on one article and review it the way you'd review a patch. First one's free, no card required.