Claude Code Content Pipeline for a Bilingual Next.js Blog

Because a single content item can touch several coupled files, and a partial edit is a silent production bug. Staging turns the agent's output into a proposal that one deterministic script validates in full — key trees, metadata, assets — before it writes anything into the repository.
Key-tree drift: an extra bullet point or a renamed section written in one language and not the other. It does not fail loudly, it just breaks one locale of one page in production, which is why equality of the two key trees should be a hard check rather than a review item.
Only if validation covers everything that can fail during the write. A script here validated all the content correctly, then failed partway through writing because a constant it inserts after had moved to another module — leaving the repo half-merged. Anchor resolution and path existence belong in the validation phase.
Fetch them with a script that reads the author and licence from the source API rather than typing them by hand, then store the result in one typed registry that the article components spread into a figure component. One image reused by three posts then carries one attribution that can be corrected in a single place.
Good at structure, consistency of house style, and producing a natural second language. Bad at knowing what is true and at restraint — every specification number needs a source, and a fixed section skeleton per post type prevents the fifth section that repeats the second.

Key Takeaway
Publishing a bilingual post here touches five coupled files, so the agent never edits them directly: it writes a staging folder, and one script validates key-tree parity, translation coverage and metadata before writing anything. The guard rails, not the writing, are what make agent-generated content safe to ship.
This site has several hundred articles, each existing twice — once in English and once in Indonesian — with the same key tree, the same components and the same structured data. That is a content problem that looks like a writing problem and is actually a consistency problem, and consistency is exactly where a language model is weakest and a script is strongest.
So the pipeline is built around a division of labour: the agent writes prose and picks structure, and deterministic scripts decide whether that output is allowed into the repository. Here is how it works, including the failure it hit today.
A single published article is spread across the codebase because different parts of the framework need different pieces of it.
Editing five files by hand for one post is tedious; editing them for twenty is a guarantee of drift. The mismatch does not fail loudly either — a missing key breaks one locale of one post in production, which is the kind of bug that survives for months.

The rule that makes the whole thing safe is that the agent produces a proposal, not an edit. Everything lands in a staging directory, and a merge script is the only thing allowed to touch the repository.
# The agent never edits the five coupled files by hand. It stages, then
# one script validates everything and only then writes anything.
/tmp/blog-staging/agent-a/
registry.json # slug, category, dates, cover, SEO title and description
en.json # the English fragment, keyed by translationKey
id.json # the Indonesian fragment, same key tree
faqs.json # five bilingual question and answer pairs
node scripts/merge-blog-staging.mjs # validates, then edits 5 repo files
node scripts/merge-blog-faqs.mjs # adds FAQPage schema data
npx tsc --noEmit && npm run build # parity guard runs inside the buildThe script checks a specific list before it writes a byte: unique slug and translation key, a valid category and month, a real date, the cover file existing on disk, the component file existing, the two locale key trees being identical, no characters that would break the message format, and complete metadata. Only then does it edit the five files.
Every check in that list exists because something went wrong once. The five that earn their place most often are these.
None of these are clever. They are all cheap, mechanical, and they turn a class of silent production bug into a script that refuses to run.
Make the validator runnable against already-published posts too, not just staged ones. Retrofits bypass the staging path entirely, and a hand-edited translation is exactly where key-tree drift creeps back in.
Writing this article, the merge script failed in the most instructive way possible: it validated everything correctly, began writing, and then threw partway through.
Error: anchor not found in lib/blog-posts-static.ts:
export const REAL_IMAGE_SLUGS = new Set([
# What had happened: the constant moved to lib/blog-display.ts months ago.
# The merge script validated the staged content perfectly, started writing,
# updated messages/en.json, messages/id.json and the post registry, and
# THEN failed on an anchor it could no longer find.
# The repo was left half-merged: translations present, metadata missing.
# Re-running would not help either, because the script skips slugs that
# already exist in en.json — it considered the work done.The cause was an anchor. The script inserts new entries after known lines of code, and one of those constants had moved to a different module some time ago. Because anchors are resolved at write time rather than during validation, the script had already updated both message files and the post registry before it discovered it could not find the third target. Worse, re-running was not a recovery path: the script skips slugs it finds already present in the English messages, so it considered the job done.
A validate-then-write script is only atomic if validation covers everything that can fail during the write. Anchor resolution, file permissions and path existence all belong in the validation phase — otherwise you have a script that is careful about your data and careless about its own preconditions.
The same principle extends to assets. Cover images are generated from the site's own template, so they are always the right dimensions, always on-brand, and never a stock photo whose licence nobody checked. In-article images are downloaded from Wikimedia Commons by a script that reads the author and licence from the API and writes them into a typed registry.
# Covers are generated from the site's own template, not downloaded.
node scripts/generate-blog-cover.mjs --manifest covers.json
# In-article images are fetched from Wikimedia Commons, re-encoded, and
# their author and licence are read from the API rather than typed by hand.
node scripts/fetch-blog-inline-images.mjs \
--manifest images.json --report report.json
# The report becomes a typed registry, so one image reused by three posts
# carries exactly one credit line that can be corrected in one place.
export const BLOG_FIGURES: Record<string, BlogFigureSource> = { ... };That registry is the part I would recommend to anyone doing this. Because credits live in one file rather than being retyped in each article, an image reused by three posts carries one attribution string, and correcting a wrong one is a single edit instead of a search across the catalogue.

After a few hundred posts through this pipeline, the division is clear and slightly counter-intuitive.
The honest summary is that the model is a strong drafter and an unreliable fact-checker, so the harness has to supply the facts and verify the shape.
If you maintain content in a repository, four steps get you most of this.
The interesting part of agent-generated content is not the writing, which is the easy half. It is building a harness strict enough that a confident, fluent, occasionally wrong writer cannot break production — and then keeping that harness honest, including on the day it discovers a bug in itself.