The Slack message arrived at 10:47 p.m. on a Friday. A tester had paid in Stripe sandbox, the webhook fired, the subscription row showed up in the database, and the web app still said "Free plan."

I knew where to look. I had written the feature three weeks earlier and I still remembered the shape of it. What I had not remembered, because nobody remembers until it hurts, is that the word on screen and the value in the API were not guaranteed to stay in sync unless something forced them to.

The bug was never in one repository

The API stored plan status as an integer enum. The web app mapped that integer to a label through a shared constant file. The admin panel had its own table column with a human-readable badge.

All three were correct on Monday. By Friday someone had added a new trial state in the API migration, updated the web app copy, and forgotten the admin badge color. Three small edits in three places that were supposed to represent the same fact.

In a multi-repo setup that is three pull requests, three reviews if you are lucky, three deploy pipelines, and a window where production shows three different truths depending on which URL you open. On a Friday night you do not have the patience for that window. You have a tester refreshing the billing page and wondering if your product works.

In CastorStack all four applications live in one monorepo: marketing site, customer web app, admin panel, and .NET API. The shared contracts sit in shared/. The product name and locale list sit in config/. When the enum changes, TypeScript and C# both see the same source of truth within a single commit.

That Friday fix was one branch, one diff, one push. The API returned the new state, the web app read the shared mapping, the admin panel picked up the same enum name. No deploy order puzzle. No "did Vercel finish before Azure?"

What people fear about monorepos

The objection I hear most often is noise. Change the button color on the marketing hero and CI runs tests for the admin panel. Fair concern, and also a solved one if the pipeline is scoped.

CastorStack's CI does not treat every file as equal. A change under apps/web-app/ does not need to block on an unrelated marketing-site spec unless you touched shared contracts. The goal is not one giant job that runs forever. The goal is one place where you can grep the whole product when a bug crosses boundaries.

The second objection is access control. "Marketing contractors should not see billing code." True for large teams. For a solo founder or a two-person shop, everyone already sees everything because everyone is everyone. Splitting repositories before you have a permission problem buys organizational purity at the cost of Friday-night coherence.

The third is tooling fear: "Monorepos need Nx or Turborepo or custom scripts." Sometimes they do at scale. At launch scale you need a root package.json that knows how to run each app, a single lockfile discipline, and the habit of putting shared types in one folder instead of copy-pasting interfaces. CastorStack ships that layout so you are not inventing it on week two.

The moment the monorepo pays for itself

It is never the first commit. The first commit feels heavy because the folder tree is large.

It pays off the first time a feature touches more than one surface. Billing touches the API webhook handler, the customer subscription page, the admin subscription list, and often a line of copy on the marketing pricing table. Authentication touches every app plus CORS configuration plus email templates.

Count the surfaces. That is the number of repositories you are volunteering to keep aligned if you split early.

A monorepo does not remove coordination. It moves coordination into Git history instead of into your calendar. You see, in one diff, that the webhook handler and the UI string changed together. Code review, even self-review the next morning, catches drift before users do.

What a monorepo does not give you

It does not replace product decisions. You still choose whether trial users see premium features. You still choose whether the admin can cancel a subscription on behalf of a customer. The repository shape does not answer those questions. It only makes the consequences of your answers visible in one place.

It also does not replace deployment independence. CastorStack deploys the marketing site to Vercel, the two Vite apps to their own Vercel projects, and the API to Azure Container Apps. Same repo, four deploy targets. The monorepo is about source alignment, not about shipping everything as one binary.

And it is not free complexity forever. If you grow into separate teams with separate release trains, splitting a bounded context out can make sense. Most products never reach that day. Most products do reach the day a paying user finds a mismatch between what the invoice says and what the dashboard shows.

Why this matters when building got cheap

Generating a React page or a C# controller takes minutes now. The expensive part is wiring the page, the controller, the migration, the admin view, and the email copy so they still mean the same thing after your third iteration.

A model can write each file in isolation. It cannot, by itself, guarantee that isolation survives contact with production. A monorepo with shared contracts is how you give the model, and yourself, a single map.

CastorStack exists so you start from that map: four apps, shared configuration, tests and CI already aware of the boundaries. The Friday-night fix I described is not a selling point because it is glamorous. It is a selling point because it is ordinary work that becomes expensive when the repository layout fights you.

If you are choosing where to spend launch weeks, spend them on the feature only your customers care about. Let the repository argue for coherence on its own.