Skip to content
tutorial

Build 15 Client Sites From One Codebase

February 20, 2026 Updated September 17, 2026 By Alex Crabinsky
Build 15 Client Sites From One Codebase

Five client sites in five repos is fine. Fifteen is where it turns on you: a header bug is fifteen fixes, a schema tweak is fifteen pull requests, and the shared components you copied in year one have quietly become fifteen slightly different components.

The codebase Amrify sells is the one that runs our own live sites, built to run hundreds of sites, and soon thousands, (roofing, TV mounting, glass, exterior cleaning) from a single Astro monorepo. This post is the architecture, including the parts we got wrong the first time.

The Shape of the Repo

pnpm workspaces hold the packages; Turborepo decides what to build:

monorepo/
├── apps/
│   ├── client-site-a/        # a complete Astro site
│   ├── client-site-b/
│   └── client-site-c/
├── packages/
│   ├── layouts/               # page layouts, routes, JSON-LD, OG images
│   ├── shortcodes/            # UI components used inside content
│   ├── config-loader/         # reads and merges each site's config
│   └── types/                 # shared Zod schemas
├── pnpm-workspace.yaml
└── turbo.json

One rule holds the whole thing together: apps import from packages, packages never import from apps. The moment a package reaches into a site, that site can no longer be deleted or cloned safely.

What Lives in Shared Packages

layouts owns everything a visitor sees on every site: the base HTML shell, navigation, footer, blog and service page routes, structured data, sitemap and social-card generation. A site’s page is a few lines:

---
import Base from "@amrify/layouts/layouts/Base.astro";
import BlogHero from "@amrify/layouts/loaders/BlogHero.astro";
---

<Base title={title}>
  <BlogHero />
</Base>

Fix a focus-ring bug in Base.astro once and every site picks it up on its next build. There is no second copy to forget.

What Lives in Each Site

Each site is mostly src/config/ and src/content/. The config is plain JSON, split by concern (site identity, SEO and business data, theme, menus), and the shared layouts read it at build time:

{
  "siteName": "Client Site A",
  "base_url": "https://clienta.com",
  "logo": "/images/client-a-logo.svg",
  "logo_text": "ClientA"
}

Same layout code, different JSON, different site. Two sites on this system can share every line of code and still look like different studios built them, because the header, hero and section variants are switches in that config rather than forks of the components.

Theming With Tailwind CSS 4

Tailwind 4 reads its design tokens from CSS, so per-site theming is a matter of feeding each site’s colors into @theme. The colors come from the site’s config; the design system turns them into custom properties the shared components already use:

/* generated per site from src/config/theme.json */
@theme {
  --color-primary: #2563eb;
  --color-secondary: #1e40af;
  --font-sans: "Inter", sans-serif;
}

No tailwind.config.mjs per site, no theme code to keep in sync. Change a hex value in JSON and every button, link and section on that site follows.

Content With a Schema

Every site’s Markdown is validated against Zod schemas from the shared types package (full guide here):

const services = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/services" }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    hero: heroSectionSchema,
    cta: ctaSectionSchema.optional(),
  }),
});

A missing field on any site fails that site’s build with the file and field named. Content mistakes stop at the build instead of at the client’s inbox.

Build Only What Changed, Deploy One Site at a Time

Turborepo knows the dependency graph, so a change to a shared package rebuilds the sites that depend on it and nothing else:

# build only the sites affected by the last commit
turbo run build --filter=...[HEAD^1]

Each site deploys on its own. Shipping a fix to one client never waits on, or risks, another. Turbo’s remote cache means a site whose inputs did not change is not rebuilt at all, which is what keeps a thirty-site CI run tolerable.

Adding a Site

In the Amrify repo the scaffolder copies a site, rewires its URL and registers it in the fleet:

  1. Run the new-site command from an existing site or trade template
  2. Fill in src/config/ (identity, colors, menus, business data)
  3. Replace the content in src/content/
  4. Build, run the cross-site verifier, deploy

Hand the business details to Claude Code or Cursor with the bundled CLAUDE.md, and a first draft of a new client site is about an hour’s work, with the diff reviewed like a pull request. The verifier then walks every site’s routes, metadata and structured data before anything deploys.

Mistakes to Skip

Sharing too early. A component two sites use is not shared yet; it is two sites that happen to agree. Promote to packages/ when the third site wants it, and not before. Once it is shared, changing it means rebuilding everyone.

Letting versions drift. Hoist dependencies to the workspace root and pin one version per package. pnpm catalogs let you declare that version once in pnpm-workspace.yaml and reference it from every site.

Merging shared changes on faith. A CI job that builds every affected site before a shared package change lands is the only thing standing between you and thirty broken homepages at once. We run it on every pull request; it is the most valuable job in the repo.

Forking a shared file “just for this site.” It works for a month. Then a fleet-wide fix silently misses that one site, and you find out from a client. Extend shared layouts through slots and config, never through copies.

Where to Start

If you are managing four or more sites, this architecture pays for itself on the first fleet-wide fix. Amrify Pro ships it as built: pnpm workspaces, Turborepo, the shared packages, per-site config and theming, the new-site scaffolder and the cross-site verifier, with the ten trade templates as ready-made starting points. One payment covers unlimited sites, and updates to the shared packages are included for life.

The free Amrify Starter (public repo at launch) is the same design in miniature: two demo sites and five shared packages, so you can learn the pattern before you need it at scale.


Already running a fleet? Before you consolidate anything, get a baseline. The free Website Monitor keeps up to 30 sites on one watch list, re-runs Google’s Lighthouse test on demand, and keeps the last ten results per site, so you can see which clients drift and by how much. It needs a free account and no card; nothing about it is scheduled or automatic, you click to re-test.

Tags: tutorial
Share:

Ready to build your next agency site?

Start with the free tools today and the free Starter at launch, or own Amrify Pro - every niche template in one purchase.

Get Amrify Docs