The Multi-Site Challenge

Running multiple content sites means managing multiple databases, multiple deployments, and multiple SEO strategies — unless you architect it differently. When we expanded from AIKit (ai-kit.net) to AiSalonHub, PlayableAd Studio, CCFish, and DeFiKit, we made a deliberate choice: one D1 database, one EmDash instance, multiple sites. Here's how it works.

Why One Database?

The traditional approach to multi-site content management is to spin up a separate CMS instance per site. That means duplicate infrastructure, duplicate maintenance, and duplicate SEO tooling. With EmDash and Cloudflare Workers, we can run all projects from a single D1 database — partitioned by collection, taxonomy, and route logic.

Partition by Collection

EmDash uses collections to separate content types. Each project gets its own content type structure:

```

posts (AIKit blog)

posts-aisalonhub (salon listings)

posts-ccfish (game updates)

posts-playablead (ad templates)

```

Each collection has its own schema, fields, and SEO configuration. The EmDash admin UI auto-generates forms from the schema — no custom admin panels needed per project.

Single D1, Zero Database Sprawl

D1's SQLite-based architecture makes multi-tenancy straightforward. A single database with partitioned tables means:

- **One backup** instead of four

- **One migration** instead of four

- **One wrangler config** instead of four

The Publishing Pipeline

All four projects share the same automated publishing pipeline. Here's the architecture:

Queue-Based Content Generation

```

[Cron Trigger] → [Hermes AI generates JSON] → [queue-publisher.py] → [D1 Insert] → [Live on CDN]

```

The cron job runs Mon/Wed/Fri at 6AM (and optionally more often during content sprints). It checks a queue directory for pending posts, publishes them via wrangler D1 execute, then archives to a published/ subdirectory.

Collection-Aware Routing

When a blog post is inserted, the plugin checks its category and taxonomy to route it to the correct site. A post tagged with "aisalonhub" renders on AiSalonHub's blog section. A post tagged with "aikit" renders on the main AIKit blog.

Technical Implementation

D1 Schema for Multi-Site

```sql

CREATE TABLE ec_posts (

id TEXT PRIMARY KEY,

slug TEXT,

status TEXT DEFAULT 'draft',

title TEXT NOT NULL,

content JSON,

excerpt TEXT,

category TEXT,

tags JSON,

site TEXT, -- 'aikit' | 'aisalonhub' | 'ccfish' | 'playablead'

created_at TEXT,

published_at TEXT

);

```

The `site` column is the key differentiator. It's set at publish time based on the queue file's category or metadata.

Site-Specific Sitemaps

Each site has its own sitemap that filters by the `site` column:

```typescript

// sitemap.xml.ts for ai-kit.net

const db: D1Database = (env as any).DB;

const result = await db

.prepare("SELECT slug FROM ec_posts WHERE status='published' AND site='aikit'")

.all();

```

This gives each project its own SEO footprint without needing separate databases.

Cost Analysis

Running a multi-site blog network on one D1 database:

| Resource | Single DB Approach | Multi-DB Approach | Savings |

|----------|-------------------|-------------------|---------|

| D1 reads/month | ~100K | ~400K | 75% |

| Workers invocations | ~50K | ~200K | 75% |

| KV operations | ~10K | ~40K | 75% |

| Maintenance hours | ~2hrs/month | ~8hrs/month | 75% |

| Total monthly cost | ~$5 | ~$20 | 75% |

The savings aren't just financial — they're operational. One database means one place to monitor, one place to optimize, and one place to debug.

Lessons Learned

1. **Start with partitioning from day one.** Adding a `site` column later requires migrating existing data.

2. **Use tags for cross-site content.** Some posts apply to multiple projects — tags handle this better than duplicating rows.

3. **Collection-level caching.** Each site's content is cached independently via Cloudflare's Cache API. A publish on one site doesn't invalidate the others.

4. **Monitor D1 storage limits.** D1 has a 10GB storage limit per database. For text-heavy content (no images), that's roughly 500,000 posts — plenty of room.

When NOT to Share a Database

If your projects have vastly different performance requirements (one is read-heavy, another is write-heavy), separate databases make more sense. But for content sites with similar traffic profiles, sharing a D1 database is the most efficient architecture.

The Bottom Line

For indie makers bootstrapping multiple content projects, a shared D1 database with partitioned collections is the most cost-effective approach. It's the architecture behind 78 published posts across 4+ projects — and it costs less than a cup of coffee per month to run.