Architecture Decisions
The fork we took, and why
Seven public architecture decisions — what we chose, what we gave up, and when we'd revisit each.
"We use Logto" is an answer; "why not Auth0" is the useful content. Each ADR below (Architecture Decision Record) is something we actually weighed, shipped, and wrote down. The full versions live in GitHub at docs/adr/ — this page is the short-form index for teams about to make similar calls.
ADR-001
2026-05-03
Logto for cross-brand SSO
Four domains, one reader identity, solo-team budget, embedded sign-in needed. After weighing NextAuth / Auth0 / Clerk, we chose Logto Cloud: pricing curve survives 10× growth, Experience SDK is first-class, OSS escape hatch.
ADR-002
2026-04-01
OpenNext on Cloudflare Workers (not Vercel)
Single-vendor strategy for a multi-site portfolio: Workers + D1 + KV + R2 + Queues on one bill. Cost: working around a few Next.js features like ISR (see ADR-006). Vercel DX is better, but at 4 sites the math doesn't.
ADR-003
2026-05-06
Single Resend audience, brand-tagged
Per-brand audiences look clean, but you can never answer "how many readers are active in ≥2 brands." We chose shared audience with brand tags — cross-brand queries are one JOIN. Cost: strict scoping on broadcasts.
ADR-004
2026-05-06
D1 for cross-brand analytics (not KV, not external warehouse)
Ad-hoc analytics needs SQL. KV can't, external warehouses cost too much at our scale, Workers Analytics Engine query surface is too narrow. D1 flat table + JSON metadata is the sweet spot; trade-off is no multi-region replication.
ADR-005
2026-05-03
Per-brand cookies, no shared session across brands
Different TLDs fundamentally can't share cookies; we use OIDC redirect for "sign in once, use any brand." Costs ~300ms on first cross-brand visit. Buys brand independence and security isolation.
ADR-006
2026-05-08
No ISR on OpenNext until Queues are wired
Learned via PR #60: next.revalidate on Workers needs Cloudflare Queues or it 500s in production. Until Queues are set up, use Suspense islands + worker-local caches instead.
ADR-007
2026-04-15
One repo per sub-brand, group repo for shared admin
Rejected monorepo: a broken build in one brand shouldn't block another. Cost: code duplication, but 6 months in it's been manageable. Revisit when team grows past 3 engineers.
For the full versions (including options we rejected and revisit triggers), the complete ADR set is public at docs/adr/.
See Advisory services →