The Multi-Site Challenge
When you manage multiple web properties, the instinct is to spin up separate hosting, separate databases, and separate CI/CD pipelines for each one. For a two-person dev team running both **AIKit.net** (a B2B marketing platform) and **AiSalonHub.com** (a Vietnamese salon directory), that approach would burn through budget and dev time.
Instead, we deployed both sites on a **shared Cloudflare stack** — same account, same edge network, same tooling — with isolated D1 databases and R2 buckets per project. Result: 60% lower infrastructure costs and zero cross-project interference.
Why Cloudflare Workers?
Both sites are built on **Astro 6 + EmDash CMS**, deployed to Cloudflare Workers. Here's why that architecture works for multi-site:
| Factor | Traditional Hosting | Cloudflare Workers |
|--------|-------------------|-------------------|
| Monthly cost per site | $20–50 (VPS or PaaS) | $0–5 (Workers free tier) |
| Cold start | N/A (always-on) | <5ms (isolates per request) |
| Database per site | Separate instances | Separate D1 databases |
| Global CDN | Extra cost | Included |
| Deploy time | 2–5 min | 30 sec |
With Workers, each site gets its own `wrangler.jsonc` pointing to a dedicated D1 database. The Cloudflare dashboard keeps all resources organized under one account, but the Workers themselves have zero knowledge of each other.
The D1 Isolation Pattern
The key trick: **one Cloudflare account, many D1 databases**. Each project creates its own:
```bash
AIKit.net database
npx wrangler d1 create ai-kit-net
AiSalonHub.com database
npx wrangler d1 create aislonhub-db
```
Each project's `wrangler.jsonc` binds only its own database:
```json
{
"d1_databases": [
{ "binding": "DB", "database_name": "aisalonhub-db", "database_id": "fcbf1694-97b6-4cb2-9067-1a56b613311c" }
]
}
```
This means **zero data leakage** between sites, even though they share the same Cloudflare account and auth credentials. The Workers runtime enforces isolation at the binding level.
R2 Bucket Per Project
Media files follow the same pattern. Each site gets its own R2 bucket:
| Site | R2 Bucket | Purpose |
|------|-----------|---------|
| AIKit.net | `ai-kit-media` | Blog images, product screenshots |
| AiSalonHub | `aisalonhub-media` | Salon photos, service icons, AR Try-On assets |
Both buckets live under the same Cloudflare account. The free tier includes 10 GB of storage per account, which is plenty for both sites at current scale.
Domain Routing with Cloudflare DNS
Each site gets its own domain, but both route through Cloudflare DNS:
| Site | Domain | Worker Route |
|------|--------|-------------|
| AIKit.net | `ai-kit.net` | `api-kit-net.pages.dev` (Cloudflare Pages) |
| AiSalonHub | `aisalonhub.com` | Custom Worker route |
The DNS is managed from one Cloudflare dashboard. SSL certificates auto-provision for both domains. The only headache: `aisalonhub.com` still has a lingering DNS conflict with a Lovable.dev project, which is being resolved.
CI/CD: One GitHub, Two Deploy Scripts
Both sites live in separate directories under `~/Projects/AIKitLLC/`. Deploying is a one-liner per project:
```bash
Deploy AIKit
cd ~/Projects/AIKitLLC/EmDash && npm run deploy
Deploy AiSalonHub
cd ~/Projects/AIKitLLC/AiSalonHub && npm run deploy
```
The EmDash build pipeline (`astro build && wrangler deploy`) is identical for both sites. The only difference is which `wrangler.jsonc` file gets read, which determines the D1 database and R2 bucket bindings.
Cost Analysis
Running two sites on separate VPS instances would cost $20–40/month. On Cloudflare Workers:
| Resource | Cost/Month |
|----------|-----------|
| Workers (2 sites, free tier) | $0 |
| D1 databases (2 x 1 GB) | $0 |
| R2 storage (under 10 GB) | $0 |
| DNS + SSL | $0 |
| **Total** | **$0** |
Beyond free tier limits (100k requests/day, 1 GB storage), the pay-as-you-go pricing still beats any static hosting for dynamic content.
Real-World Deployment Flow
Here’s the actual sequence we follow when deploying a content update to AiSalonHub:
```
1. Edit content in EmDash admin UI or seed file
2. Run `npx emdash types` to regenerate TypeScript types
3. Run `astro build` (8–12 seconds)
4. Run `wrangler deploy` (15–20 seconds)
5. Verify via curl: `curl -sI https://aisalonhub.com/services`
```
Compare this with Shopify: save a product → wait for CDN cache invalidation (up to 24 hours on some plans). Wix is faster (5–10 min) but still slower than edge-deployed Workers. The **30-second deploy cycle** means AiSalonHub can push urgent content updates—a new salon promotion, an updated price list, a seasonal service package—in under a minute from commit to live.
Monitoring Both Sites from One Dashboard
A hidden benefit of the shared stack: **observability**. Cloudflare’s analytics dashboard shows both sites in one view:
| Metric | AIKit.net (30-day) | AiSalonHub (30-day) |
|--------|-------------------|--------------------|
| Requests | 48,200 | 3,150 |
| Bandwidth | 1.2 GB | 280 MB |
| CPU time | 1,850 ms | 420 ms |
| Cache hit ratio | 68% | 72% |
| Error rate | 0.03% | 0.01% |
Because both Workers are deployed from the same account, we can see performance side-by-side without switching between platforms. This is especially valuable for the AiSalonHub launch phase, where we need to verify that the site handles traffic spikes after SEO improvements kick in.
The Git Workflow Challenge
One area that still needs work: **repository management**. AIKit.net lives in `~/Projects/AIKitLLC/EmDash/` with full git history. AiSalonHub was set up in `~/Projects/AIKitLLC/AiSalonHub/` but lacks an initialized git repository.
The fix is straightforward:
```bash
cd ~/Projects/AIKitLLC/AiSalonHub
git init
git add .
git commit -m "Initial commit: AiSalonHub EmDash site scaffold"
gh repo create AiSalonHub --private
git push origin main
```
This becomes more important as content volume grows. Without version control, schema changes and seed updates can’t be rolled back. We’ll be adding this on the next maintenance cycle.
What You Learn
Running a multi-site architecture on Cloudflare Workers teaches you to think in **resource bindings** rather than server instances. Each site is just a configuration file pointing to isolated storage primitives. The shared infrastructure (edge network, auth, DNS, CI/CD tooling) is free.
For a small team managing niche sites like AiSalonHub alongside a flagship product like AIKit, this pattern keeps operational costs near zero while maintaining full data isolation. It's the serverless equivalent of having separate servers without paying for separate hardware.