On April 19, two Cloudflare Pages deployments fired every time we published anything to dxdev.com. One for dxdev-landing, the marketing site. One for dxdev-blog, a completely separate Astro project sitting at the same domain. We had been running this arrangement for months and calling it architecture. It was an accident we had stopped noticing.

It meant double config, double deploy, and a standing question every time we wanted to add a page. Which repo owned this? Marketing? Blog? Something in between? The answer kept shifting, and neither repo accumulated the shared context it needed to answer cleanly. The blog’s Astro content schema had grown to track series, episode, and reader tier. dxdev-landing knew none of it. The landing site’s dev-host config, which let us preview the marketing site on a local network, had never been threaded through to the blog. Two surfaces serving a single product had stopped talking to each other.

Folding dxdev-blog into dxdev-landing was the obvious fix. We expected to spend an afternoon moving files. We spent the day doing product architecture.

The merge exposed a schema nobody had written down

The blog’s content model had grown in its own repo, loosely, responding to needs as they surfaced. Carrying it into dxdev-landing meant making it explicit.

The frontmatter for a post in the merged repo now carries eight fields that weren’t previously shared: series, episode, tier, tags, component, concern, phase, and status. Each exists because a specific rendering question needed an answer that prose alone couldn’t give. tier distinguishes free posts from paywalled content. component tracks which part of the product stack a post addresses. concern is narrower than tags, pointing at the operational problem rather than the general topic. phase marks where in a project arc the work happened.

None of these are decorative. A renderer that doesn’t understand tier can’t decide whether to show a paywall. A renderer that doesn’t understand series can’t build episode navigation. This is where content schema crosses from editorial metadata into rendering contract, and the merge was what made the crossing visible.

It also left us with a question: how many fields can pile up in frontmatter before the post renderer becomes a CMS by stealth? We don’t have the threshold yet. What we have is the recognition that since dxdev-landing now owns both surfaces, this schema is shared infrastructure. Adding a field is no longer an editorial call. It ships with the next deploy.

What the renderer forgot on the way over

Moving content is easier than moving capabilities.

dxdev-blog had a renderer feature we’d built for a post called “The 32-Day Revolution,” written from multiple narrator viewpoints on the same arc of events. We’d added perspective-tab rendering for it, a mechanism for toggling between two framings of the same moment in the text. When we folded the blog into dxdev-landing, that capability dropped silently. The Astro component that handled it didn’t transfer cleanly, and the merged site’s renderer gave no indication anything was missing.

A content migration validates that text moved. It does not validate that rendering behavior moved with it. The perspective tabs on “The 32-Day Revolution” would have collapsed into a broken layout for any reader who hit that post on the new site. Nothing in the migration output flagged it. Catching it required knowing to look.

Cleaning up while both surfaces were open

With both surfaces open at once, a handful of links on the public-facing pages turned out to point at GitHub repos that had been renamed or frozen since we’d written the posts that referenced them. It was not urgent. But with both surfaces mapped to the same mental model at the same time, fixing them then was cheaper than re-opening the context later.

Dead GitHub links are just wrong. Cleaning them at the merge point cost a few minutes and would have cost an afternoon of re-deriving context on a separate day.

One deploy now carries both concerns

The practical change from this day is that dxdev-blog is frozen. Every future post, every renderer update, every schema change goes into dxdev-landing. Three posts moved from review to published on this day. The dev-host config got threaded through properly so we could preview the combined site on a local network. The GitHub Actions workflow dropped to a single run pushing to a single Cloudflare Pages project.

That sounds like a simplification, and it is. It also comes with coupling worth being direct about. When the blog and the marketing site share a deploy, a content edit and a homepage redesign go out in the same release window. A broken schema field in a draft post can hold back a marketing change. The two concerns are coupled now, and the coupling is real.

A few months ago we wrote about splitting a monorepo into several smaller repos because the coordination tax of keeping everything together was climbing faster than the simplicity gains. This is the inverse move. We collapsed two repos into one because they’d already converged on the same deploy target, the same Astro config, the same audience. The earlier split separated independent concerns. This merge recognized that dxdev-landing and dxdev-blog were never really independent, and the coupling they’d been hiding behind two repo names was always there.

The schema was the work

Getting the text out of dxdev-blog and into dxdev-landing was the smallest part of the day. The perspective-tab renderer for “The 32-Day Revolution” needed reconstruction, since the Astro component hadn’t transferred cleanly. The frontmatter schema had to be negotiated deliberately: which of the eight fields belonged in the shared contract, and which were dxdev-blog artifacts that could be dropped.

When a blog folds into a product site and both have to ship together, the frontmatter fields stop being editorial notes. The tier field in dxdev-landing determines whether a paywall component renders, and getting it wrong ships a content access bug to every visitor. Deciding what tier means when both surfaces share a single Cloudflare Pages deploy, and whether concern lives in the shared rendering contract or stays a local annotation, was the architecture. The file migration was mechanical. That part wasn’t.

So when two sites merge, check that rendering behavior moved along with the text, because a migration can report success while a feature like the perspective tabs is gone. And expect content edits and marketing changes to go out in the same release window from then on.