ADR-005: Multisite via Host Resolution
Status: Accepted
Superseded by ADR-007 single-domain consolidation. Host resolution survives only as a shim:
SiteResolverfalls back to the single site (ICurrentSite.Id) for any unmatched host, and EF global query filters onISiteScopedkey on that id. Date: 2024-12
Context
Need to run multiple branded sites (general, programming) from single backend.
Options:
- Separate deployments per site
- Path-based routing (/general/…)
- Host-based resolution
Decision
Use Host header resolution → SiteContext per request.
general.example.com → site_id=generalprogramming.example.com → site_id=programmingImplementation
Backend (Request Pipeline)
Request → SiteContextMiddleware → SiteResolver → SiteContext ↓ DB lookup (cached)ISiteResolver/SiteResolver: Handles host → site lookup with 10min cacheSiteContextMiddleware: Integrates SiteResolver into request pipeline, stores in HttpContext.ItemsHttpContextExtensions.GetSiteId(): Helper to retrieve site from context
Frontend (React)
SiteContext.tsx: Fetches site config from/api/site/contextendpointuseSite()hook: Provides site config to components
Dev override: ?site=general query param (backend converts to {site}.localhost).
Data Isolation
| Entity | Scoping |
|---|---|
| Work | site_id (primary) |
| Edition | site_id (denormalized) |
| ReadingProgress, Bookmark, Note | site_id |
| User | Global (cross-site account) |
Consequences
Pros
- Single deployment
- Shared codebase and infrastructure
- Per-site theming/SEO
- Clean domain separation
Cons
- Must enforce site_id filter everywhere
- Risk of data leakage if filter missed
- Site cache invalidation needed
Notes
- Sites table stores: code, primary_domain, theme, ads_enabled, indexing_enabled
- SiteDomains table for domain aliases