Why the location of content is a strategy decision
Search engines treat a subdomain as a separate site. Links, authority and topical depth earned on sub.example.com barely transfer to example.com, and a crawler that sees a thin apex domain next to a deep subdomain has no reason to connect the two. If your strongest content lives on the wrong host, it is not doing its job for the pages that matter most to you. That mismatch — depth on a subdomain, thinness on the apex — is exactly the situation we were in.
The decision to consolidate is usually obvious in hindsight. The work is in doing it without losing the rankings, the links or the readers. Google's guidance on site moves is the right starting point and it understates how much of the job is bookkeeping.
An audit gate, not a bulk move
Moving everything would have moved our weakest content along with our strongest. Before any migration we ran an editorial audit over the whole library and set a bar: an article only travels if it is long enough to be a real reference and grounded in more than one source domain. In our case the gate was a floor of 900 words plus citations to independent sources. Twenty-nine articles passed and the rest stayed behind or went back for work first. The floor is checkable after the fact: the shortest of the twenty-nine runs to 1,073 words and the longest to 3,612, and the median article cites three distinct external domains — the full library runs to 56,489 words across 76 different source domains.
The audit is worth doing even when you are not migrating. We describe the full method in the entry on auditing generated content; the short version is that word count and source diversity are the two numbers that best predict whether a page will survive being the canonical version of a topic.
Redirects that outlive the pages
Every old URL must 301 to exactly one new URL, and the mapping needs to survive both with and without a trailing .html, because the old site exposed both forms at different times. We generated 58 redirect rules for 29 articles — two per article — and deployed them as a static _redirects file, which Cloudflare Pages processes before it serves any file. That ordering matters: a redirect rule outranks a same-named static file, so the old pages can even remain on disk as a fallback without ever being served.
Two details are easy to get wrong. First, the redirect target must be the canonical, extensionless URL of the new page — redirecting to a URL that itself redirects wastes a hop and can confuse crawlers. Second, verify the redirects from the outside after deploy: fetch the old URL with curl -I and confirm a 301 with the new location, because a typo in the rules file fails silently until a reader hits it.
Sitemaps and the duplicate question
Once the articles lived in two places — physically on the old host, canonically on the new one — the old sitemap had to stop listing them. We rebuilt the old site's sitemap to drop every migrated URL, created a new sitemap for the apex section, and added that sitemap to the main sitemap index. The net effect is that no URL is listed in two sitemaps, and every old URL resolves to a 301 rather than to a duplicate page. The sitemap protocol is unambiguous about this: a URL should appear in exactly one sitemap, and a sitemap should only contain URLs that resolve to 200s.
For the following weeks we treated duplicate content as the primary risk: the crawl would take time to re-learn the new canonicals, and during that window the old URLs had to keep redirecting rather than serving 200s. They did, because the redirects file was deployed before the sitemap changed — order matters as much as correctness.
What broke anyway
Two things broke despite the checklist. The first was images: the migration rewrote absolute URLs inside the articles, and the asset-copy step that should have mirrored the images ran its pattern against the already-rewritten URLs, so it matched nothing and the images were quietly never copied. The fix was to collect the asset list from the files before touching any URL, then copy — and then to assert it, which is now a release check: every image reference in the migrated tree is resolved against disk before the deploy is allowed to run. The lesson: in any migration script, resolve the full list of resources first, rewrite URLs second.
The second was a control-character bug in the migration output — a stray byte at the start of some lines that came from a literal escape sequence in the script. It did not break rendering, but it corrupted parts of the HTML in ways that were invisible until a full-text scan. The scan for stray control characters is now part of every release check; it costs a second and it caught the same bug a second time in a different site's build pipeline.
The checklist
- Audit the library and set a quality gate; only the strongest content travels.
- Map every old URL to exactly one new canonical URL, including the
.htmlvariant. - Deploy the redirects before changing any sitemap.
- Remove migrated URLs from the old sitemap; add the new section to the sitemap index.
- Collect the asset list from source files before rewriting URLs; copy images explicitly.
- Verify from the outside: old URL → 301 → new URL, no duplicates in any sitemap.
- Run a control-character and dead-link scan over the whole migrated tree.
Done in that order, a 29-article migration is a quiet operation: no duplicate pages, no broken images, no dead ends. The rankings take time to settle, but the structure is clean from day one — and a clean structure is what lets the content start earning its keep on the domain where it actually matters.