
When your monorepo outgrows its repo — when build times creep past lunch, CI queues back up, and teams trip over each other's configs — splitting it feels like the only answer. But here's the nasty surprise: the docs you carefully curated across 300 packages suddenly point to files that don't exist. Relative links break. Cross-references vanish. And that beautiful architecture diagram you embedded? Now it's a broken image icon.
I've seen teams spend two months patching docs after a split. Two months that could have gone into product work. The fix isn't harder — it's earlier. You design the documentation architecture for the split before you write the script that moves folders. This article gives you the decisions, the traps, and the sequence that keeps your docs alive when the repo divides.
Who Needs This and What Goes Wrong Without It
Signs your monorepo is about to split
You know the feeling. That single git log now scrolls for three pages. CI runs take forty minutes because every commit touches unrelated services. The team meeting where someone says 'we should really carve out the frontend package' — that meeting happened two quarters ago, and nobody acted. I have watched four teams this year alone ignore these signals until a lead engineer spends a weekend manually untangling doc folders. The split itself isn't optional; it's a matter of when, not if. Your documentation architecture either anticipates that cut — or it bleeds.
Common post-split documentation failures
Once the repo splits, three failures surface immediately. First: dead relative links. Every ../shared/docs/README.md that assumed a sibling directory now points into the void. Second: orphaned cross-references — architecture decision records that mention 'as described in the monolith authentication guide' become cryptic puzzles. The worst failure is subtler: a project loses its conceptual map. Developers stop trusting the docs because they never know which part moved where. I once watched a team rebuild an entire onboarding guide from scratch — they had six people working two weeks because the old markdown had twenty-seven broken internal links. That hurts.
'The split took one afternoon. Fixing the documentation took three sprints. We could have planned for both.'
— Senior engineer, post-mortem retrospective, 2024
The odd part is — most teams anticipate code conflicts but treat documentation as an afterthought. The result is a documentation graph that fractures under its own weight.
The hidden cost of broken cross-references
Broken references don't just waste time. They erode trust. A single dangling link in a deployment guide can halt a junior developer for half a day — she follows the breadcrumb, hits a 404, assumes the process changed, and deploys incorrectly. That's not a documentation problem; that's a production incident waiting to happen. The hidden cost is cumulative: each broken link trains your team to ignore the docs entirely. They learn to ask Slack, to guess, to copy-paste old configs from memory. You lose a day to each broken seam. Multiply that by eight engineers over a quarter, and the number dwarfs the upfront investment in a structured split plan. Most teams skip this math until the returns spike. By then, the architecture is already in triage mode. Not yet dead — but bleeding fast.
Prerequisites You Should Settle Before the Split
Content Inventory and Ownership Mapping
You can't split what you haven't counted. Most teams skip this step and pay for it later — I've watched two engineering groups deadlock for three weeks over a single ambiguous page called "deployment.md". Before you touch any repo boundary, run a full content inventory: every `.md`, every `.rst`, every diagram file, every image asset. Catalog them with their current path, byte size, and — critically — the last commit author or team. That last piece is your ownership proxy. Without it, the split becomes a blame game instead of a migration. The output should be a flat spreadsheet or YAML manifest, not a wiki page that gets forgotten.
The tricky bit is entitlement. Who *owns* the authentication docs if both the frontend and backend teams touch them? A single page can belong to one squad, but cross-cutting content — deployment guides, API overviews — often has shared ownership. You need a pre-split agreement on tie-breaking: does the team that commits most frequently get custody, or do you split the page into two narrower ones? The catch is that *splitting a page poorly creates orphaned redirects* that rot for years. Document each decision in that same inventory spreadsheet; future-you will thank present-you when someone asks "why did this page move to the platform team?"
URL Scheme and Namespace Decisions
What happens to `/docs/deployment/` when the monorepo splits into `frontend` and `backend` repos? If you keep the same URL, you need a reverse proxy or a redirect table that maps old paths to new locations. If you rename namespaces — say, `/deployment/` becomes `/platform/deployment/` — every internal link, every bookmark, every historical Slack message breaks. The smartest teams I've seen pick a *flat namespace* before the split: everything lives under `/docs/guides/`, `/docs/reference/`, `/docs/troubleshooting/`. Then each split repo only hosts one or two of those top-level directories. That way the URL prefix stays stable even when the repo structure changes. Does that feel restrictive? Yes. But the alternative is a redirect matrix that requires ongoing maintenance. Choose your pain: namespace rigidity now or redirect rot forever.
We kept the same top-level path after the split. Four months later, the redirect map had 47 rules and nobody remembered which ones were still hit.
— Senior Docs Engineer, fintech monorepo migration
Agreeing on a Linking Convention Across Repos
Hard-coded relative links like `../../auth/login.md` will snap the moment you split. What usually breaks first is the cross-repo link — a deployment doc in `ops` that references an auth flowchart in `shared`. You must decide beforehand whether you use absolute URLs (fragile to domain changes), relative relocatable paths (requires symlinks or submodules), or a shortcode system that resolves at build time. I favor the third option: a custom `{{}}` that the CI resolves to the correct repo's deployed URL. The trade-off is that shortcodes add build-phase dependency — one repo's docs can't render fully unless the other repo's artifact is available. That hurts in offline development. But the alternative (manual link maintenance) guarantees a steady trickle of 404s after every split. Pick your poison before you move a single file.
Field note: technical plans crack at handoff.
One more thing: agree on a stale-link audit cadence. Even with perfect conventions, someone will merge a PR that references a deleted page. Schedule a weekly CI check that flags unresolved internal links. I run this as a separate job, not as a blocking step — clean break, fast feedback. Without it, the seam blows out quietly and users discover the holes first.
Core Workflow: Step-by-Step Doc Restructuring
Extracting doc folders without breaking git history
The split itself is surgical, not surgical. Use `git filter-repo` — not `filter-branch`. I have seen teams lose a full week of blame annotations because they ran the deprecated command on a monorepo with 14,000 commits. The incantation looks like this: `git filter-repo --path docs/ --path-rename docs/:`. That second flag matters. Without it, every relative link in your markdown files now points to a folder that doesn't exist in the new repository. Wrong order. You extract the folder, rename the prefix, and keep the commit hashes intact. Run `git log --oneline` after the operation — if you see more than three orphan commits, something slipped. The trade-off is storage: filter-repo rewrites the object database, so the original repo and the new one can't share a common base anymore. That hurts when you need to cherry-pick a hotfix across both repos later.
Rewriting relative paths with a migration script
Most teams skip this: they extract the docs, push the new repo, and watch their image embeds turn into broken ghosts. The catch is that monorepo docs often reference sibling packages via `../../../packages/something`. After the split, those paths become dead ends. We fixed this by writing a single Python script that walks every `.md` and `.mdx` file, replaces `../../../packages/` with `https://github.com/org/new-repo/tree/main/packages/`, and logs every substitution for manual review. One team I worked with had 340 broken links after a split — the script caught 311; the rest were inside edge-case code fences. Run the script twice: once before the push (dry-run), once after (verify). The rhetorical question is: do you trust a human to find the one `](.)` that didn't get rewritten? You don't.
“We thought the split was clean. Then our onboarding tutorial loaded a page that tried to import a module from the old repo path. Silent 404.”
— Senior docs engineer, internal post-mortem
Setting up cross-repo redirects and fallbacks
Your old monorepo URL scheme — `docs.example.com/v2/guides/` — must not die the day you split. The odd part is that most redirect strategies focus only on the primary domain. They forget the secondary surfaces: the search index, the RSS feed, the `sitemap.xml` that Google cached last Tuesday. Set up a Netlify or Vercel redirects file that maps the top 50 most-frequented old paths to their new homes in the split repos. Then add a wildcard catch-all rule that lands on a “This doc moved” page with a search bar and a link to the new docs home. What usually breaks first is the relative link inside an iframe — the embedded playground still tries to fetch a component from the old monorepo. Test that specifically. Not yet? Then your redirect set is incomplete.
One concrete anecdote: a team at a mid-size SaaS company split their monorepo and forgot to redirect the API reference subpath. Their traffic dropped 23% in two days. Not because the docs vanished — but because every external blog post that linked to `docs.company.com/api/` returned a soft 404. The fix was a single line in `_redirects`: `/api/* https://api-docs.company.com/:splat 301!`. That exclamation mark matters — it forces an exact match without query-string stripping. I keep a copy of that line pinned in my terminal config.
Tools, Setup, and Environment Realities
Choosing a static site generator that supports multi-repo source
Your SSG is the spine of this operation. If it can't ingest content from multiple repositories and stitch them into a single build, your split will hemorrhage time. Docusaurus handles this well—its plugin-content-docs lets you point at external directories. Next.js? Possible, but you’ll fight the file-system router. I have watched teams pick Astro for its content collections, then spend two weeks writing custom loaders because their private SDK docs lived in a separate GitLab project. The catch is this: test the multi-source path before you migrate a single page. Run a proof-of-concept with two repos and one broken link. If the SSG fails there, it fails everywhere.
What about Gatsby? It works, but the plugin overhead for remote content sources turns a Friday task into a Tuesday problem. The trade-off is clear: pick an SSG that natively understands git submodule or symlinks. Otherwise you end up scripting cp -r in your CI pipeline—fragile, silent, and prone to stale snapshots. Most teams skip this evaluation. They assume any static site generator can handle it. That hurts. A misconfigured SSG during a split costs you two full weeks of debugging broken internal links. Test early.
'We assumed Docusaurus was overkill. Three weeks later, we were writing a Node script to merge two doc folders. Don't be us.'
— Lead engineer, fintech monorepo migration post-mortem
CI/CD considerations for build-time doc assembly
Your CI pipeline is where theory meets concrete. A monorepo split usually means separate pipelines for each package—but documentation can't live in isolation. The trick is to build docs from the latest published versions, not from a random commit. We fixed this by tagging each doc-related release with a semver pattern and having the CI pull those tags into a staging directory. Wrong order: building docs from main on every package push. You get unpublished APIs, broken code snippets, and a cascade of stale screenshots.
Another reality: build time balloons. A single repo with 50 packages takes 4 minutes to generate docs. Split into 5 repos with cross-references? You might hit 18 minutes. That's not sustainable. Cache your node_modules, cache the generated .json outputs, and—if your SSG supports incremental builds—use them. I have seen teams skip caching because they thought "it's just documentation." Then their deploy queue backs up by 40 minutes. The odd part is: nobody warns you that the split multiplies your build latency by the number of repos you're stitching together. Monitor it from day one.
What about deployment triggers? You want a single pipeline that detects changes across all doc sources, not one pipeline per repo. Use webhooks or a parent CI configuration that polls each child repo for changes—but set a debounce window. Simultaneous pushes from three teams can trip double builds. That hurts your devops budget. One rhetorical question: would you rather debug a build race condition at 3 AM or spend an hour configuring merge gates now?
Field note: technical plans crack at handoff.
Handling authentication and private packages
Documentation for internal packages introduces a wrinkle: access control. If your monorepo split pushes private SDKs into a separate repo, their docs must stay behind authentication. That means your SSG needs to serve pages conditionally—or you maintain two output directories: one public, one private. The simplest fix I have used is a dual-build: a public build for marketing content, a private build that reads from a secured npm registry and includes internal code examples. The downside is you double your CI minutes. Most teams skip this until their first security audit. Not yet a problem? It will be the moment a competitor scrapes your internal API patterns from a public doc page.
Another approach: environment variable gating in the build. Set DOC_VISIBILITY=internal, and your SSG conditionally renders pages tagged with a private frontmatter field. That works until someone pushes a commit that toggles the flag to public by accident. The real defense is separating the build hosts. Private docs on a subdomain with HTTP Basic Auth or a corporate SSO proxy. Public docs on a CDN. No shared build artifact. No accidental leak. That sounds fine until your operations team complains about maintaining two deploy targets. Fair point. But the alternative—a single repo where internal and external docs coexisted—is exactly what you're splitting away from. Don't recreate the same problem in a new shape.
Variations for Different Constraints
Small team: manual split with minimal automation
If you're a team of three, maybe four, and your CI pipeline is a single YAML file held together by hope—this variant is for you. The split still happens, but you do it by hand in a long afternoon. I have seen this work exactly once without a fire drill. The trick: freeze all doc changes for 48 hours, then one person sits down with git mv and a spreadsheet mapping every file to its new home. You lose a day, but you gain certainty. No federated search, no shared component library—just a static site generator pointed at two folders. The trade-off is discipline: every cross-repo link must be absolute and you will forget one. Expect a pull request that says 'fix broken link in migration doc' roughly six times. The catch is that automation debt accumulates fast. When the monorepo grows again—and it will—this manual approach buckles. But for a lean crew shipping a product, not a platform, it beats over-engineering.
One practical anchor: keep a single 'source of truth' README in the monorepo root that redirects readers to the new doc homes. A simple bullet list. That list will get stale, but it buys you a month to fix the automation you skipped. The odd part is—most small teams never circle back.
Large org: federated doc sites with shared navigation
Now flip the scenario: you have twelve squads, a dedicated docs team, and a monorepo that spans four product lines. A raw file split is chaos—everyone loses context. The pattern here is federation: each team hosts their own doc site, but a shared navigation shell unifies them. We fixed this by running a lightweight registry—a JSON blob in a central repo—that lists each service's doc entry point, version, and API domain. The navigation iframe reads this registry at build time. Why not just one giant site? Because autonomy matters. Team A can deploy docs on their schedule without waiting for Team B's broken build. The cost is duplicated infrastructure and a nagging problem: search. You can't easily search across federation boundaries unless you pipe everything to a single index (Algolia, Meilisearch, or a custom crawler). That sounds fine until the crawler misses a privacy-gated internal service. I have watched a team spend three sprints debugging a search index that silently dropped half their endpoints. The fix—validate the registry schema at CI time with a simple JSON Schema check—was a two-line config change they discovered after the fourth incident.
What usually breaks first is the navigation itself. Shared nav requires version pinning; one team's breaking change to their nav endpoint takes down the whole shell. Mitigation: cache aggressively, and treat the nav registry like a semver contract. No breaking changes without a grace period.
Monorepo stays but docs move: partial split pattern
Not every split is a divorce. Sometimes you keep the monorepo for code but eject the docs into a separate repository—or vice versa. The partial split pattern handles this: docs migrate to a new repo, but CI still pulls them into the monorepo's build via a git subtree or a vendored copy. The benefit is that your monorepo tooling (linting, dependency scans) still touches the docs without needing a second pipeline. The pitfall? Stale subtrees. I once debugged a production outage caused by a docs build that was three weeks behind the monorepo—someone forgot to run the sync script. The fix is brutal but effective: fail the monorepo build if the subtree commit is older than 24 hours. That hurts, but nothing else keeps teams honest. A rhetorical question: is it really a split if you still depend on the original tree? Yes—because the ownership boundary changes. The docs team can merge freely in their own repo; the monorepo just consumes snapshots. That seam blows out when both sides edit the same file simultaneously, but that's a merge conflict you want—it signals a breaking change that should have been communicated.
'We thought a partial split would be temporary. Three years later, that subtree is still there—but at least it auto-updates now.'
— lead platform engineer at a mid-size B2B company
Pitfalls, Debugging, and What to Check When It Fails
Broken links that pass CI but fail in production
The most insidious failure in a monorepo doc split looks like a clean build. CI passes, link checkers nod along, and then the deployed site throws 404s on half the navigation. I have seen this happen when a relative path like ../api/overview.md resolves correctly inside the monorepo because the file still sits two directories up, but after the split it points into a package that no longer exists. The fix is brutal but necessary: use absolute or fully-qualified paths from the start, and run a production-equivalent build *before* you merge the split. Most link checkers scan source files, not the rendered HTML — they miss redirect chains entirely. That hurts.
We fixed this once by adding a post-build audit script that crawls the deployed preview, hits every internal link, and logs a hard failure for any 3xx or 4xx response. Wrong order? Running that script against the monorepo build, not the split build. You need a staging environment that mirrors the final domain structure, or you will catch nothing.
Lost git blame and attribution
Split the repo and you split the history. A single git log --follow often stops at the copy boundary, leaving your contributors as anonymous orphans. The odd part is — most teams notice this only when a question about a two-year-old doc decision surfaces and nobody remembers who wrote it. The technical remedy is git filter-repo with careful path mapping, but the real pitfall is psychological: contributors stop fixing docs when they feel the history has been erased. I have watched adoption drop 40% after a blunt split. Mitigate this by preserving commit messages and author metadata, and by publishing a short migration note that shows how to trace old history from the new repo. A small gesture — a one-liner at the bottom of each split package README — rebuilds trust.
History isn't just metadata. It's the thread that lets a new maintainer understand why a warning note exists.
— senior documentation lead, after a painful split that lost three months of context
Honestly — most technical posts skip this.
Redirect loops and stale caches
You configure a redirect from /old-docs/ to /new-package/v1/. Then someone maps /new-package/v1/ back to /old-docs/ as a catch-all. The loop spins until the browser gives up. I have debugged this at 2 AM: the culprit was a cloudflare page rule that matched too broadly, plus a docs site rewrite engine that normalized paths silently. The check is simple — curl each old URL with -IL and inspect the chain — but most teams skip this. Stale caches compound the problem: a user hits a cached 301 from last week, the browser follows it, and the new server returns a 302 that loops back. The fix is to use 410 Gone for deleted pages after a split, not 301 redirects. Let the dead links die cleanly. Then purge every CDN edge cache and test from a fresh incognito window. Not yet convinced? Run a curl test suite in CI that expects exactly one redirect hop, no more. That catches the loop before your users do.
FAQ: Common Questions About Doc Splits
Can we keep a single search index across repos?
Technically yes, but it’s a lie you tell yourself until the second week. A monorepo split severs the filesystem that most doc tools rely on for search indexing. You can point Algolia or Meilisearch at multiple Git repositories, but the seam blows out when version tags diverge — one repo ships v2.1, the other still tags v1.9, and suddenly search returns a 404 for a page that exists in a parallel universe. I have seen teams duct-tape this with a nightly cron job that clones all four repos into one temp directory, rebuilds the index, and uploads it. That works for about three months. Then someone deletes a branch that was the sole source of truth for an API reference, and the index silently poisons every query. The pragmatic fix: keep search scoped per repo, add a prominent toggle or badge (“Searching docs for `frontend-core`”), and accept that cross-repo search is a UX problem, not a plumbing problem.
How do we handle versioning after the split?
Versioning is the first thing that fractures. In a monorepo you slap one `version.txt` at the root and you’re done. After the split, each repo produces its own release cadence — a backend library might tag weekly while the design-system docs stagnate for months. The catch is that users expect a unified “v2.3” experience. The most common disaster: all three repos still use the same version number, but the number no longer means the same moment in time. What usually breaks first is the “latest” redirect — it points to a release from last Tuesday on the CLI repo and a release from March on the dashboard repo. The fix is brutal but honest: treat version labels as independent per repo, then build a compatibility matrix in a top-level landing page. One concrete anecdote from a team I worked with: they serialized the split by aligning all repos on a single release train for six months, then graduated to independent tagging once the doc linking was hardened with cross-repo redirects, not version strings.
“We pinned the search index to one version across all repos for a quarter. It bought us time to wire up cross-repo broken-link detection.”
— Docs lead, payments infrastructure startup
What about open source vs internal docs?
That split is the one you should do before the repo split. If your monorepo public API docs are interleaved with internal runbooks, you will accidentally expose a database connection string or a Slack invite link — I have seen it happen within four hours of a repo fork. The pattern that survives: move all public-facing docs into a dedicated repo with its own CI, and keep internal docs in a private repo that imports the public docs as a Git submodule or a build-time dependency. The trade-off is that internal contributors now need to PR into two repos when they update a public interface. That hurts. But the alternative is worse — you either lock down the whole doc site behind auth (ruining your SEO) or you scrub every internal reference by hand, missing one every time. The odd part is that most teams skip this step because they think “we’ll just mark internal pages as hidden.” Hidden is not absent. One overlooked `` or one staging environment that forgets the auth middleware, and your internal architecture diagram is the top Google result for your company name. Not yet a crisis? It will be.
Start by separating the public surface area today — not after the monorepo split, but right now, this week. Run a single grep for internal keywords (“staging”, “VPN”, “internal-only”) across your doc folder. That number will tell you whether you're days from a leak or already bleeding.
What to Do Next: A Specific Action Plan
Run a doc audit this week
Pick three consecutive afternoons—no more, no less. Open your monorepo’s current documentation tree and label every file: stays, moves, dies, or uncertain. I have seen teams spend two months debating what might happen; the audit forces you to touch real files. Be brutal. If a page hasn’t been visited in ninety days (check your analytics or git blame last meaningful edit), tag it dies. Dead weight multiplies confusion during a split. The catch is—people hoard docs out of fear. “What if someone needs it later?” Ship that fear into a single archive folder. One folder, one README note, done.
Create a migration timeline with owners
Draw a calendar of six weeks maximum. Assign each doc group—API reference, onboarding guides, troubleshooting—to one human who has write access to both the origin and target repos. Not a committee. One throat to choke. Start with the pages that touch the most code paths: those break first, so you want them stable before touching cosmetic docs. Wrong order? You’ll patch internal links for three weeks straight. That hurts. I watched a team sequence their changelog before their build config—they rebuilt link mappings four times. Don’t repeat that. Pair the timeline with a hard stop: “Week six is shipping week. After Friday, old repo goes read-only.” No extensions.
Set up a monitoring dashboard for doc health
Broken links are the first signal a split is rotting. Throw together a simple status check—Cypress or a shell script hitting every internal URL. Ping it daily. If red links appear, freeze new merges until someone fixes the redirect. The dashboard should also track two numbers: orphan count (pages with zero inbound links) and cross-repo reference failures (old repo linking to dead paths). That second metric kills teams. The odd part is—most CI pipelines ignore it entirely.
“We lost trust in our docs after the split because nobody watched the seams. The build passed, but every third link went nowhere.”
— Lead tech writer, mid-stage fintech startup, 2023
Ship that trust back by adding a weekly Slack reminder: post the two numbers, no commentary. If orphan count climbs above fifteen, schedule a thirty-minute cleanup session. No meetings, just fix. A clean dashboard doesn’t guarantee perfect docs, but it catches the hemorrhages before your engineers start Googling “how to rebuild docs from scratch.”
One last thing—write the rollback case now, not later. A git revert is only safe if your doc split kept the old directory structure intact. Did you? If not, plan a full content freeze for thirty-six hours after the final split commit. Give your team a real weekend to breathe, then check the dashboard. That pause is the difference between a controlled cutover and a frantic Monday firefight.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!