monorepo scaffolding shared-components cross-site-linking bulk-import astro turborepo

Bulk Site Scaffolding with Shared Components — Scaling from 2 to 14 Sites

Bulk Site Scaffolding with Shared Components

Problem

Started with 2 sites (zendee-adventures, squamish-ai) and needed to import 12 more businesses from diverse sources: GitHub repos, Lovable apps (lovable.app), Instagram profiles, personal websites, and LinkedIn. Each site needs its own brand config, CLAUDE.md context, visual identity, and full Astro page structure — but they should share navigation patterns and link to each other.

Investigation & Approach

1. Discovery: Identifying businesses across platforms

  • Used gh repo list veganpolice --limit 50 --json name,description to scan GitHub repos
  • Used WebFetch to scrape Lovable app URLs for business details
  • Attempted Instagram and LinkedIn scraping (both login-gated, got partial data)
  • Presented a curated list for the user to confirm/reject (filtered out personal projects, forks, tools)

Key insight: Not all repos are businesses. Present candidates and let the user decide. Some are “projects to track” not active businesses — the system should accommodate both.

2. Bulk scaffolding: 11 agents in parallel

Rather than running /site-scaffold 12 times sequentially (which asks for brand info each time), we:

  1. Read the template files from apps/zendee-adventures/ once
  2. Defined brand config for each business based on available context
  3. Launched 11 parallel agents, each scaffolding one complete site
  4. All 11 completed in ~80 seconds total (vs ~15 min sequential)

Template files per site (13 files):

  • package.json, astro.config.mjs, tsconfig.json
  • brand.config.ts, CLAUDE.md
  • public/_headers
  • src/styles/brand.css, src/styles/global.css
  • src/layouts/SiteLayout.astro
  • src/pages/index.astro, src/pages/blog.astro, src/pages/blog/[...slug].astro
  • src/content.config.ts

3. Shared component extraction

After scaffolding, all 14 sites had identical header/footer markup (~590 lines duplicated). Extracted to @repo/ui:

  • SiteHeader.astro — takes name + navLinks[] props
  • SiteFooter.astro — takes name prop
  • BusinessDirectory.astro — renders grid of all businesses, excludes current site

Central business registry: packages/ui/src/data/businesses.ts — single source of truth for all business names, slugs, domains, taglines, and pages URLs.

4. CI/CD for 14 sites

The existing deploy workflow already looped over apps/*/dist, so it scaled automatically. Added:

  • Auto-create Cloudflare Pages projects on first deploy (no manual wrangler setup)
  • workflow_dispatch support for deploying specific sites or all

Key Design Decisions

1. Parallel agents for bulk scaffolding

Why: 11 sequential scaffolds would take 15+ minutes and block the conversation. Parallel agents complete in ~80 seconds with identical quality.

Pattern: Read template once → define per-site config → launch N parallel agents → verify all build.

2. Props-based shared components (not config-importing)

Why: SiteHeader takes name and navLinks as props rather than importing brand.config.ts internally. This keeps components pure — no cross-package config dependencies. Each site controls its own nav links.

<!-- Good: explicit props -->
<SiteHeader name={config.name} navLinks={[
  { label: 'Blog', href: '/blog' },
  { label: 'Contact', href: '#contact' }
]} />

<!-- Bad: component imports config internally -->
<!-- Creates hidden coupling, harder to customize per-site -->

3. Central business registry separate from brand configs

Why: BusinessDirectory.astro needs to know about all businesses, but importing 14 brand.config.ts files would create circular deps. Instead, packages/ui/src/data/businesses.ts is a lightweight registry with just the display data.

Trade-off: Data is duplicated between brand.config.ts and businesses.ts. Acceptable because the registry is the source of truth for cross-site linking, while brand config is the source of truth for per-site behavior.

4. Auto-create Cloudflare Pages projects in CI

Why: Running wrangler pages project create locally requires auth. Adding auto-create to CI means new sites deploy automatically on first push with zero manual Cloudflare setup.

# Auto-create if project doesn't exist
if ! npx wrangler pages project list | grep -q "^$site_name$"; then
  npx wrangler pages project create "$site_name" --production-branch=main || true
fi

Prevention / Best Practices

  1. When adding a new site: Use /site-scaffold <slug> for single sites, or batch with parallel agents for multiple. Always add to the businesses table in CLAUDE.md AND packages/ui/src/data/businesses.ts.

  2. When shared markup appears in 3+ sites: Extract to @repo/ui. Keep components props-based, not config-importing.

  3. When importing from external sources: Use /content-sync <slug> to pull real content from GitHub repos, Lovable apps, etc. into the scaffolded sites.

  4. Nav links vary per site — this is by design. Each site’s index.astro controls its own navLinks array. Common patterns: Blog + Contact, Blog + Get Involved, Blog + Follow.

Files Created/Modified

New shared components (packages/ui):

  • src/components/SiteHeader.astro
  • src/components/SiteFooter.astro
  • src/components/BusinessDirectory.astro
  • src/data/businesses.ts

New sites (12):

  • apps/aaronr/, apps/adventure-oasis/, apps/adventure-weddings/
  • apps/audacious-art/, apps/create-makerspace/, apps/function-growth/
  • apps/healthcal/, apps/hot-tub-every-day/, apps/mountain-life/
  • apps/pirate-radi0/, apps/sendy-adventures/, apps/southside-lodge/

New skill:

  • .claude/skills/content-sync/SKILL.md

Modified:

  • All 14 sites’ pages (42 files) — replaced inline header/footer with shared components
  • .github/workflows/deploy.yml — auto-create projects, workflow_dispatch support
  • CLAUDE.md — businesses table updated to 14 entries

Cross-References

  • See also: spa-scraping-and-second-wave-scaffolding.md (Phase 2: 14→24 sites)