Public-route allow-list — keep it in sync when adding domains
Public-route allow-list
In any app with an auth gate, there's a spot that decides which routes anonymous users may see. That spot is usually a public-route allow-list. When you add a new domain and forget to update it, users hit informational pages and get bounced to login.
1. The incident shape
| Situation | What the user sees | Real cause |
|---|---|---|
Anonymous user opens /pharmacy → redirected to /login?returnUrl=/pharmacy |
"Why do I need to log in to read this?" | Allow-list missing /pharmacy |
| Search-engine crawler can't index informational pages | SEO penalty | Same cause — the crawler is anonymous |
| New domain merges with no allow-list update | User has to report it; you patch post-hoc | Sync obligation missed |
2. Common matcher shape
There are two kinds of routes anonymous users should see:
- Exact paths:
/,/login,/signup,/help - Prefix matchers:
/pharmacy,/pharmacy/seoul/12345(whole domain)
A prefix matcher lets a single startsWith line cover an entire domain — the most robust form.
function isPublicRoute(pathname: string): boolean {
if (pathname === "/" ) return true;
if (pathname.startsWith("/login")) return true;
if (pathname.startsWith("/signup")) return true;
// informational domains ↓
if (pathname.startsWith("/pharmacy")) return true;
if (pathname.startsWith("/price-compare")) return true;
if (pathname.startsWith("/long-term-care")) return true;
// ...
return false;
}
A function beats a string-union type here — you need dynamic matching like startsWith.
3. The 7 spots to sync when adding a domain
This is spot #7 of the same 7-spot sync from dashboard widget uniformity.
New-domain PR checklist
├─ Route registration (app/x/page.tsx)
├─ Dashboard widget
├─ Sidebar
├─ Footer link
├─ i18n ko/en
├─ Search category
└─ Public-route allow-list ← this spot
When this single spot is missed, anonymous users can't even reach the domain — maximum user impact for one of the easiest items to overlook.
4. Before / after
| Aspect | Allow-list missing | Allow-list in sync |
|---|---|---|
| Anonymous entry | Forced login redirect | Informational page renders |
| Crawler indexing | Fails | Succeeds |
| New domain discovery | "Why can't I see it?" → user reports | Smooth launch |
| SEO | Penalty | Unaffected |
| Regression detection | After the user complains | At PR review |
5. Adopting this in your own project
- At your auth gate (middleware · ClientLayout · server guard) make a single SSOT
isPublicRoute()function. - Add "registered in publicRoute" as a checkbox on your new-domain PR template.
- Default to prefix matchers (
startsWith) so all sub-paths are covered automatically. - Separate "informational" vs "auth-required" routes in code with a comment block, so review can spot category drift.
- Add an e2e regression — anonymous fetch returns 200 for informational domains and 308 for auth domains. Count the buckets.
One anonymous user shut out might be a search engine. The allow-list is a small spot, but it decides the width of your public surface.