Going Global with
Subdomains & Subdirectories
A pragmatic strategy for expanding Unscammed to US, UK, and Australia without fragmenting the brand, the SEO, or the engineering team. Subdirectories for marketing, subdomains for app and API, per-region Aurora for GDPR, DynamoDB Global Tables for shared intelligence.
Executive Summary
Three constraints drive the architecture: GDPR data residency for UK/EEA users, engineering economy (one codebase, many regions), and SEO equity for a new brand that cannot afford fragmented domain authority.
The core decision
One domain, two patterns. Use subdirectories (unscammed.com/uk/) for the marketing site to preserve SEO authority. Use subdomains (uk.app.unscammed.com) for the app and API because only DNS-level routing can satisfy data residency requirements.
Path-based routing cannot give us GDPR compliance — /uk/ is parsed by the application after the request has already reached a single origin. Subdomains resolve to distinct regional endpoints before any code executes.
Why This Split
Two surfaces, two sets of constraints, two patterns. The marketing site optimises for SEO; the app optimises for data residency and security.
Subdirectories for Marketing
Why it wins: A new brand cannot afford to fragment domain authority across three separate subdomains. Google treats uk.unscammed.com as a separate site that must earn rank from scratch.
- SEO:
unscammed.com/uk/inherits the root domain's authority - Infra: One SSL cert, one CDN config, one Strapi instance
- Content: Region becomes a locale parameter, not a deployment target
- New countries: Translation + content task, not infra project
Subdomains for Marketing (rejected)
Why it loses: All the SEO work done on unscammed.com today would not transfer to uk.unscammed.com. We would be starting from zero in each country.
- Separate backlink profiles per subdomain
- Separate authority score per subdomain
- More SSL certs to manage
- No technical upside for static marketing content
Subdomains for App & API
Why it wins: Only DNS-level routing satisfies data residency. uk.api.unscammed.com resolves to an eu-west-2 endpoint before any application code runs.
- Residency: UK PII physically cannot be served from us-east-2
- Security: Eliminates the 4 spoofable detection methods from the dossier
- Scaling: Each region scales independently
- Cookies: Natural scoping prevents accidental cross-region auth
Path Routing for App (rejected)
Why it loses: app.unscammed.com/uk reaches a single origin; the /uk is parsed in application code. That is the exact fragile pattern the dossier is moving away from.
- Cannot satisfy GDPR Art. 44 — data could be in either region
- Region remains spoofable via URL manipulation
- Forces app-layer region detection back into code
- Blocks independent per-region scaling
The Domain Pattern
One table, one source of truth. Every URL across the platform follows this pattern.
| Surface | Pattern | Why |
|---|---|---|
| Marketing SEO |
unscammed.com/us/unscammed.com/uk/unscammed.com/au/
|
Subdirectories inherit the root domain's SEO authority. No user data → no residency concern. |
| App Product |
us.app.unscammed.comuk.app.unscammed.comau.app.unscammed.com
|
Subdomains enable DNS-level routing to regional Aurora clusters. Region is infrastructure, not a header. |
| API Backend |
us.api.unscammed.comuk.api.unscammed.comau.api.unscammed.com
|
Per-region API Gateway → per-region Aurora. Kills all four spoofable region-detection methods. |
| KYT API Banks |
kyt.unscammed.com |
Latency-based Route 53 routing. Nearest region's DynamoDB + DAX. Sub-15ms response time for bank customers. |
| Admin Internal |
admin.unscammed.com |
Single global admin UI. Region dropdown selects which regional API to call. One login, any region. |
Frontend Strategy
Marketing stays as one SvelteKit deployment with region as a URL segment. The React app ships as a separate build per region, each with its API URL baked in at build time.
3.1 Marketing Site (unscammed-website)
Single SvelteKit deployment on the existing Website Prod EC2. Region is a URL segment resolved in the [region] layout route. Strapi provides localised content via a locale field on each entry.
hreflang tags pointing to the other regional variants so Google indexes each version correctly instead of flagging duplicate content.
3.2 Landing Redirect (CloudFront Function)
A CloudFront Function on the root domain performs the geo-redirect. Keep it small — CloudFront Functions have a ~1ms CPU budget.
3.3 App Frontend (unscammy-frontend)
The React/Vite app ships as one build per region, each with its own baked-in VITE_API_URL and VITE_REGION. This eliminates all client-side region detection — the one-liner window.location.hostname.replace('.com', '.co.uk') currently in Constants.js must go.
| Build | Build-time env | Deploy target |
|---|---|---|
| us-app | VITE_API_URL=https://us.api.unscammed.comVITE_REGION=US |
ECS task in us-east-2 → us.app.unscammed.com |
| uk-app | VITE_API_URL=https://uk.api.unscammed.comVITE_REGION=UK |
ECS task in eu-west-2 → uk.app.unscammed.com |
| au-app | VITE_API_URL=https://au.api.unscammed.comVITE_REGION=AU |
ECS task in ap-southeast-2 → au.app.unscammed.com |
us.app. while logged in, show a "switch region" banner — never auto-redirect an authenticated session. It breaks in-flight forms and auth state.
Linking Marketing → App
Every CTA on the marketing site links to the matching regional app subdomain. Region is a property of the page, not the user.
4.1 Region Config (single source of truth)
SvelteKit reads the region from the URL param in the layout, passes it into every page via context, and every CTA becomes <a href={region.appUrl + '/signup'}>. No hardcoded URLs anywhere.
4.2 CTA Behaviour Matrix
| Page | CTA | Target URL |
|---|---|---|
/uk/ |
Get Started | https://uk.app.unscammed.com/signup?ref=uk-landing |
/uk/ |
Log in | https://uk.app.unscammed.com/login |
/us/ |
Report a scam | https://us.app.unscammed.com/report?ref=us-landing |
/au/ |
Pricing | https://au.app.unscammed.com/pricing?ref=au-landing |
/uk/for-banks |
Request demo | https://uk.app.unscammed.com/demo?ref=uk-for-banks |
?ref= captures attribution so funnel analytics can segment app signups by marketing source.
4.3 Cross-Region Edge Cases
| Scenario | Handling |
|---|---|
User on /us/ but geolocated in UK clicks signup |
App at us.app. detects mismatch post-signup, shows soft prompt: "Switch to uk.app.unscammed.com?". Does not force. Expats and travellers have legitimate reasons. |
Logged-in UK user opens us.app. directly |
App reads region from JWT on load, redirects to uk.app. preserving the original path. Safe because state is authenticated. |
| User wants to explicitly switch region | Footer dropdown on every marketing page sets the preferred_region cookie. Future visits to unscammed.com skip the geo-redirect. |
| Marketing site needs to call an API (e.g. "check email exists") | Don't. Keep marketing → app as a full-page navigation, not XHR. Clean boundary, no CORS pain, no cross-region auth state. |
uk.app.unscammed.com do not share with us.app.unscammed.com — that is exactly what we want for data residency. If cross-region SSO is ever needed (probably for admin only), issue tokens on the .unscammed.com scope or use a central identity provider.
Backend Strategy
Every backend service deploys per region. Region becomes a deploy-time environment variable, not a request-time detection problem.
| Service | Current | Target |
|---|---|---|
| unscammy-chat FastAPI |
Single EC2 in us-east-2. Region detected from request headers via detect_region_from_headers(). |
One ECS service per region. us-east-2 and eu-west-2 at launch. Region is a deploy env var. detect_region_from_headers() deleted entirely. |
| unscammy-voice Flask |
Single EC2. Region inferred from phone-number prefix — breaks for +1 CA/US overlap. | Per-region deploy. Region determined by which regional Twilio number was dialled. Provisioning, not inference. |
| unscammy-browser-use FastAPI |
Single EC2 serving both markets. | Per-region deploy. Data-broker and regulatory targets are jurisdiction-specific anyway (UK ICO vs US state AGs). |
| unscammed-website SvelteKit |
Single EC2 with Strapi + SvelteKit. | Unchanged. Single global deployment with subdirectory routing. Strapi serves locale-specific content. |
REGION env var to select the target cluster. CI runs migrations per-region in parallel. Schema stays identical across regions — what differs is the data, not the structure.
Data Layer
Three stores, each with a distinct multi-region pattern. This is where residency and performance both live.
Aurora Serverless v2
Pattern: Per-region cluster. No cross-region replication.
- US → us-east-2
- UK → eu-west-2
- AU → ap-southeast-2
Why: GDPR Art. 44–49 compliance. UK PII physically stays in EEA. Each region scales independently.
DynamoDB Global Tables
Pattern: Shared intelligence index replicated across all regions.
- Scam patterns
- Known-bad numbers
- Domain reputation
Why: Intelligence is anonymised signal, not PII — safe to replicate globally. Eventual consistency is acceptable for lookups.
DAX
Pattern: In-region DAX cluster in front of Global Tables for the KYT API hot path.
- Sub-15ms response
- Write-through caching
- Per-region scaling
Why: The latency number that wins bank deals. This is what the Bank Platform strategy pitches.
Route 53 Routing
All region resolution happens at DNS. By the time a request reaches application code, region is decided and immutable.
| Host | Route 53 Record | Target |
|---|---|---|
unscammed.com | A → CloudFront | Marketing CDN; geo-redirect function |
us.app.unscammed.com | A-ALIAS → ALB | US frontend build in us-east-2 |
uk.app.unscammed.com | A-ALIAS → ALB | UK frontend build in eu-west-2 |
us.api.unscammed.com | A-ALIAS → API GW | unscammy-chat US → US Aurora |
uk.api.unscammed.com | A-ALIAS → API GW | unscammy-chat UK → UK Aurora |
kyt.unscammed.com | Latency-based | Nearest API GW → DAX → DDB Global |
admin.unscammed.com | A-ALIAS → CloudFront | Single admin UI; calls regional APIs via dropdown |
Admin Cross-Region Access
The AdminDataRegionMiddleware UX problem from the dossier is solved cleanly at this layer.
The Problem Today
Admins cannot access cross-region data without logging into the matching domain. A UK admin at .com cannot authenticate because their credentials are in the UK database. This is a UX dead-end the dossier explicitly called out.
The Solution
Single admin UI at admin.unscammed.com with a region selector. The UI calls the target region's API directly (us.api. vs uk.api.) based on dropdown selection. Admin identity lives in a separate global auth store (Cognito or a dedicated admin Aurora), so one login works across all regions.
Phased Rollout Plan
Delivered in five phases. Phase 3 is the GDPR milestone; Phase 4 is the bank-deal unlock.
Scope
- Restructure
unscammed-websiteroutes under[region] - Deploy CloudFront Function for geo-redirect
- Add
hreflangtags across all marketing pages - 301 redirects from every legacy URL to its
/us/equivalent - Update sitemap; resubmit in Google Search Console
Outcome
SEO-visible regional content. Zero infrastructure risk. Immediately usable for localised marketing campaigns. No backend touched.
Risks
~2 week ranking dip as 301s propagate. Recovers fully as redirects are crawled.
Scope
- Split
unscammy-frontendinto per-region builds with build-timeVITE_API_URL - Delete all client-side region detection (the
.replace('.com', '.co.uk')pattern) - Deploy
us.app.anduk.app.subdomains - Ship admin region dropdown ahead of Phase 3 so admins get familiar with the UX
Outcome
Kills all client-side region detection. Clean URL separation in place before data layer changes. Backend still single-region — two frontends hit one API.
Scope
- Stand up UK Aurora Serverless v2 cluster in eu-west-2
- Migrate UK rows from shared cluster to new UK cluster
- Deploy UK API stack (
unscammy-chat,unscammy-voice,unscammy-browser-use) in eu-west-2 - Flip
uk.api.DNS to eu-west-2 endpoint - US stack untouched throughout
Outcome
GDPR-compliant data residency. The single biggest legal and sales unblocker for UK expansion and bank deals in the EEA. Removes the GDPR Art. 44 exposure identified in the dossier.
Scope
- Migrate shared scam-intelligence index from Aurora to DynamoDB Global Tables
- Deploy DAX clusters in each region for the KYT API hot path
- Stand up
kyt.unscammed.comwith latency-based Route 53 routing - Define the eventual-consistency contract in the public KYT API spec
Outcome
Sub-15ms KYT API. The technical differentiator pitched in the Bank Platform strategy — the latency number that wins RFPs against Socure and Alloy.
Scope
- New Aurora cluster in ap-southeast-2
- New ECS services for each backend
- New Route 53 records:
au.app.,au.api. - New
/au/marketing subdirectory with translated content
Outcome
Validates the architecture's scalability. If adding AU takes more than 2 weeks, something upstream was over-fitted to the US/UK case and needs refactoring.
Risks & Mitigations
Known risks across SEO, user migration, consistency, admin UX, and cost. Each with an explicit mitigation.
SEO dilution during Phase 1
Existing unscammed.com URLs become unscammed.com/us/.... Some ranking loss is inevitable. Mitigation: 301 redirects from every legacy URL, updated sitemap, resubmission in Search Console. Expect a ~2 week dip; recovers fully as redirects are crawled.
Cross-region user migration
A US user who relocates to the UK cannot easily move their account. Mitigation: Not a Day 1 problem. When it comes up, build a one-time, user-initiated migration flow with explicit GDPR consent. Do not attempt automated detection — it will misfire for VPN users and travellers.
Intelligence Index consistency
A scam signal reported in UK should appear in US lookups within seconds. Mitigation: DynamoDB Global Tables replication is typically sub-1-second. For security-critical lookups (known-bad numbers during a live call) this is acceptable. Document the eventual-consistency contract in the KYT API spec so bank customers know what to expect.
Admin productivity during migration
Cross-region debugging is harder when data is physically split. Mitigation: Ship the admin region dropdown in Phase 2, before the data split in Phase 3. Admins get familiar with the UX before it becomes load-bearing.
Operational cost
Per-region infrastructure is more expensive than shared. Mitigation: Aurora Serverless v2 scales to near-zero when idle. ECS tasks are sized per-region traffic. Expected incremental monthly cost for the UK cluster: ~$400–600 at launch load, scaling with adoption. Cost is justified by GDPR compliance alone, before any revenue upside.
Decisions Log
Every architectural fork, what was chosen, and what was rejected. Keep this section short; add to it as the strategy evolves.
| Decision | Chosen | Rejected alternatives |
|---|---|---|
| Domain strategy | Single unscammed.com with subdomains + subdirectories |
Per-country ccTLDs (.co.uk, .com.au) — fragments SEO, higher ops cost, more certs to manage |
| Marketing routing | Subdirectories (/uk/, /us/) |
Subdomains (uk.unscammed.com) — fragments domain authority for a new brand |
| App routing | Subdomains (uk.app.) |
Paths (app.unscammed.com/uk) — cannot satisfy DNS-level residency requirement |
| Region resolution | DNS (Route 53) | Headers, JWT claims, hostname parsing, phone-prefix inference — all four current methods are spoofable |
| Cross-region user data | No replication of PII; per-region Aurora | Global tables for user data — violates GDPR Art. 44 |
| Cross-region intelligence | DynamoDB Global Tables + DAX | Aurora read replicas across regions — slower, more expensive, no value for anonymised data |
| Landing page behaviour | Geo-redirect with cookie override; skip for crawlers | Show a region picker on first visit — worse UX, lower conversion |
| App region switching | Soft prompt for unauthenticated; redirect preserving path for authenticated | Hard auto-redirect on every visit — breaks expats, travellers, and in-flight sessions |
| Admin identity | Global admin auth store; regional APIs enforce authorisation | Per-region admin accounts — creates the UX dead-end the dossier called out |
| Marketing ↔ app handoff | Full-page navigation with ?ref= attribution |
XHR/fetch from marketing to app API — CORS pain, cross-region auth state complexity |