Architecture of a Multi-Domain Astro Blog
How one Astro codebase powers three domains — eap.me, eap.dev, and eap.gg — using environment variables, GROQ queries, and a single source of truth in domain.ts.
The Multi-Domain Challenge
Building a personal brand with multiple subdomains is a common pattern, but maintaining separate codebases for each is a maintenance nightmare. This post explains how eap.me solves it with one repo, one build pipeline, and a single config file.
The domain.ts Config
Everything starts with a single file that maps environment variables to domain-specific configuration:
export type Domain = 'me' | 'dev' | 'gg';
export type Category = 'dev' | 'gaming';
export const domainConfigs: Record<Domain, DomainConfig> = {
me: { domain: 'me', category: null, title: 'Evan A Panahi' },
dev: { domain: 'dev', category: 'dev', title: 'eap.dev' },
gg: { domain: 'gg', category: 'gaming', title: 'eap.gg' },
};Category-Based Filtering
Each subdomain filters content by category. The aggregator (eap.me) has a null category, meaning it shows everything. The GROQ queries use a helper to build the category filter:
export function buildCategoryFilter(category?: Category) {
return {
filter: category ? ' && category == $category' : '',
params: category ? { category } : {},
};
}GROQ Query Design
Sanity queries are composed from reusable fragments. The field projections are shared across list and detail views:
Post list views use postFields while detail views add the body with postWithBodyFields.
Build Pipeline
Three Cloudflare Pages projects share the same Git repo. Each sets a different SITE_DOMAIN environment variable:
- eap-me: SITE_DOMAIN=me
- eap-dev: SITE_DOMAIN=dev
- eap-gg: SITE_DOMAIN=gg