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.