Merge branch 'vercel-dns-and-domain-config'
This commit is contained in:
@@ -63,6 +63,15 @@
|
|||||||
- Types in `src/types/` (shared types) or co-located
|
- Types in `src/types/` (shared types) or co-located
|
||||||
- Utils in `src/lib/` or `src/server/utils.ts`
|
- Utils in `src/lib/` or `src/server/utils.ts`
|
||||||
|
|
||||||
|
## Subdomain Routing
|
||||||
|
|
||||||
|
This project serves four product subdomains (`nessa.freno.me`, `lineage.freno.me`, `gaze.freno.me`, `inputhalo.freno.me`) plus the personal site on `freno.me`. See `docs/subdomain-setup.md` for DNS/Vercel configuration.
|
||||||
|
|
||||||
|
- **Route placement:** Subdomain pages live under `src/routes/<prefix>/*` (e.g. `src/routes/nessa/...`). The `vercel.json` host-based rewrites map each subdomain to its prefix.
|
||||||
|
- **Site context:** Use `useSite()` (SolidJS) or `getSiteFromEvent`/`getSiteFromRequest` (server) from `src/lib/site-context.ts` to detect the current site. Never host-snoop in route files — SolidStart's router can't match on host.
|
||||||
|
- **API routes:** `/api/*` is a shared pool — subdomain API requests pass through to existing routes via vercel.json pass-through rewrites (ordering matters).
|
||||||
|
- **Auth:** Host-scoped only — no cookie domain broadening.
|
||||||
|
|
||||||
## Key Differences from React
|
## Key Differences from React
|
||||||
See `src/lib/SOLID-PATTERNS.md` for comprehensive React→Solid conversion guide. Key gotchas:
|
See `src/lib/SOLID-PATTERNS.md` for comprehensive React→Solid conversion guide. Key gotchas:
|
||||||
- Signals must be called with `()` to read value
|
- Signals must be called with `()` to read value
|
||||||
|
|||||||
89
docs/subdomain-setup.md
Normal file
89
docs/subdomain-setup.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# Subdomain Setup — freno.me
|
||||||
|
|
||||||
|
This document records the DNS and Vercel domain configuration for the four product subdomains.
|
||||||
|
|
||||||
|
## DNS Configuration
|
||||||
|
|
||||||
|
**DNS Provider:** Google Domains (nameservers: `ns-cloud-a1` through `ns-cloud-a4.googledomains.com`)
|
||||||
|
|
||||||
|
Add the following CNAME records in the [Google Domains DNS console](https://domains.google.com/registrar/freno.me/dns):
|
||||||
|
|
||||||
|
| Subdomain | Type | Target | TTL |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `nessa` | CNAME | `cname.vercel-dns.com` | Automatic |
|
||||||
|
| `lineage` | CNAME | `cname.vercel-dns.com` | Automatic |
|
||||||
|
| `gaze` | CNAME | `cname.vercel-dns.com` | Automatic |
|
||||||
|
| `inputhalo` | CNAME | `cname.vercel-dns.com` | Automatic |
|
||||||
|
|
||||||
|
**After adding records:** Wait for DNS propagation (typically minutes) and for Vercel to auto-issue SSL certificates for each subdomain.
|
||||||
|
|
||||||
|
## Vercel Project Domains
|
||||||
|
|
||||||
|
Add the following domains in the [Vercel project Settings → Domains](https://vercel.com/your-team/freno-dev/settings/domains):
|
||||||
|
|
||||||
|
| Domain | Redirects to |
|
||||||
|
|---|---|
|
||||||
|
| `nessa.freno.me` | (no redirect — serves `src/routes/nessa/*` via vercel.json rewrite) |
|
||||||
|
| `lineage.freno.me` | (no redirect — serves `src/routes/lineage/*` via vercel.json rewrite) |
|
||||||
|
| `gaze.freno.me` | (no redirect — serves `src/routes/gaze/*` via vercel.json rewrite) |
|
||||||
|
| `inputhalo.freno.me` | (no redirect — serves `src/routes/inputhalo/*` via vercel.json rewrite) |
|
||||||
|
|
||||||
|
**Do NOT set any of these as the Production Branch domain** — `freno.me` remains the production domain.
|
||||||
|
|
||||||
|
## Rewrite Architecture (`vercel.json`)
|
||||||
|
|
||||||
|
The rewrites are defined in `vercel.json` with **two groups, ordered precisely**:
|
||||||
|
|
||||||
|
### Group 1: `/api/*` pass-throughs (must come first)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "source": "/api/(.*)", "has": [{ "type": "host", "value": "gaze.freno.me" }], "destination": "/api/$1" }
|
||||||
|
{ "source": "/api/(.*)", "has": [{ "type": "host", "value": "inputhalo.freno.me" }], "destination": "/api/$1" }
|
||||||
|
{ "source": "/api/(.*)", "has": [{ "type": "host", "value": "nessa.freno.me" }], "destination": "/api/$1" }
|
||||||
|
{ "source": "/api/(.*)", "has": [{ "type": "host", "value": "lineage.freno.me" }], "destination": "/api/$1" }
|
||||||
|
```
|
||||||
|
|
||||||
|
These pass API requests on subdomains straight through to the existing `/api/*` routes. This enables **dual-host Sparkle appcast support**: `gaze.freno.me/api/Gaze/appcast.xml` hits the same `src/routes/api/Gaze/appcast.xml.ts` route as `freno.me/api/Gaze/appcast.xml`.
|
||||||
|
|
||||||
|
### Group 2: `/(.*)` catch-all rewrites
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "source": "/(.*)", "has": [{ "type": "host", "value": "nessa.freno.me" }], "destination": "/nessa/$1" }
|
||||||
|
{ "source": "/(.*)", "has": [{ "type": "host", "value": "lineage.freno.me" }], "destination": "/lineage/$1" }
|
||||||
|
{ "source": "/(.*)", "has": [{ "type": "host", "value": "gaze.freno.me" }], "destination": "/gaze/$1" }
|
||||||
|
{ "source": "/(.*)", "has": [{ "type": "host", "value": "inputhalo.freno.me" }], "destination": "/inputhalo/$1" }
|
||||||
|
```
|
||||||
|
|
||||||
|
These rewrite non-API requests on each subdomain to its internal route prefix. Vercel matches top-to-bottom, so the `/api/*` rules above catch API paths first.
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
|
||||||
|
After DNS propagation and Vercel certificate issuance:
|
||||||
|
|
||||||
|
- [ ] `dig nessa.freno.me` returns the Vercel CNAME
|
||||||
|
- [ ] `dig lineage.freno.me` returns the Vercel CNAME
|
||||||
|
- [ ] `dig gaze.freno.me` returns the Vercel CNAME
|
||||||
|
- [ ] `dig inputhalo.freno.me` returns the Vercel CNAME
|
||||||
|
- [ ] `curl -sI https://nessa.freno.me/ | head -1` → `HTTP/2 200`
|
||||||
|
- [ ] `curl -sI https://lineage.freno.me/ | head -1` → `HTTP/2 200`
|
||||||
|
- [ ] `curl -sI https://gaze.freno.me/ | head -1` → `HTTP/2 200`
|
||||||
|
- [ ] `curl -sI https://inputhalo.freno.me/ | head -1` → `HTTP/2 200`
|
||||||
|
- [ ] `curl -sI https://freno.me/api/Gaze/appcast.xml | head -1` → `HTTP/2 200` (regression)
|
||||||
|
- [ ] `curl -sI https://freno.me/api/InputHalo/appcast.xml | head -1` → `HTTP/2 200` (regression)
|
||||||
|
- [ ] `curl -sI https://freno.me/ | head -1` → `HTTP/2 200` (regression)
|
||||||
|
- [ ] All four subdomains have valid SSL certificates (no browser warnings)
|
||||||
|
|
||||||
|
## Auth Boundaries
|
||||||
|
|
||||||
|
Auth remains **host-scoped** — no cookie domain broadening:
|
||||||
|
|
||||||
|
- `freno.me` web JWT cookies: host-only on `freno.me`
|
||||||
|
- Nessa: Clerk session tokens (independent)
|
||||||
|
- Lineage: mobile JWT (independent)
|
||||||
|
- Gaze/InputHalo: no web auth
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- DNS records must be added at **Google Domains** (not Vercel's DNS) since freno.me uses Google's nameservers.
|
||||||
|
- Subdomains will 404 until route files exist in `src/routes/<prefix>/*` (content tasks 05–11).
|
||||||
|
- The `bun run build` gate is worktree-friendly; run it before deploying.
|
||||||
80
scripts/verify-subdomains.sh
Executable file
80
scripts/verify-subdomains.sh
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Verify subdomain DNS propagation, SSL, and rewrite routing
|
||||||
|
# Run after DNS CNAMEs are added and Vercel domains are configured.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SUBDOMAINS=("nessa" "lineage" "gaze" "inputhalo")
|
||||||
|
APICAST_ROUTES=("Gaze" "InputHalo")
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local desc="$1"
|
||||||
|
shift
|
||||||
|
if "$@" >/dev/null 2>&1; then
|
||||||
|
echo "✓ $desc"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
echo "✗ $desc"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== DNS CNAME propagation ==="
|
||||||
|
for sub in "${SUBDOMAINS[@]}"; do
|
||||||
|
cname=$(dig +short "$sub.freno.me" 2>/dev/null | grep -i "cname.vercel-dns.com" || true)
|
||||||
|
if [[ -n "$cname" ]]; then
|
||||||
|
check "$sub.freno.me CNAME → cname.vercel-dns.com" true
|
||||||
|
else
|
||||||
|
check "$sub.freno.me CNAME → cname.vercel-dns.com" false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== HTTPS landing pages ==="
|
||||||
|
for sub in "${SUBDOMAINS[@]}"; do
|
||||||
|
status=$(curl -sI -o /dev/null -w "%{http_code}" "https://${sub}.freno.me/" 2>/dev/null || echo "000")
|
||||||
|
if [[ "$status" == "200" ]]; then
|
||||||
|
check "https://${sub}.freno.me/ → 200" true
|
||||||
|
else
|
||||||
|
check "https://${sub}.freno.me/ → 200 (got ${status})" false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Appcast regression (freno.me) ==="
|
||||||
|
for route in "${APICAST_ROUTES[@]}"; do
|
||||||
|
status=$(curl -sI -o /dev/null -w "%{http_code}" "https://freno.me/api/${route}/appcast.xml" 2>/dev/null || echo "000")
|
||||||
|
if [[ "$status" == "200" ]]; then
|
||||||
|
check "https://freno.me/api/${route}/appcast.xml → 200" true
|
||||||
|
else
|
||||||
|
check "https://freno.me/api/${route}/appcast.xml → 200 (got ${status})" false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Main site regression ==="
|
||||||
|
status=$(curl -sI -o /dev/null -w "%{http_code}" "https://freno.me/" 2>/dev/null || echo "000")
|
||||||
|
if [[ "$status" == "200" ]]; then
|
||||||
|
check "https://freno.me/ → 200" true
|
||||||
|
else
|
||||||
|
check "https://freno.me/ → 200 (got ${status})" false
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== SSL certificates ==="
|
||||||
|
for sub in "${SUBDOMAINS[@]}"; do
|
||||||
|
if echo | openssl s_client -connect "${sub}.freno.me:443" -servername "${sub}.freno.me" 2>/dev/null | \
|
||||||
|
openssl x509 -noout -checkend 0 2>/dev/null | grep -q "not expired"; then
|
||||||
|
check "${sub}.freno.me SSL valid" true
|
||||||
|
else
|
||||||
|
check "${sub}.freno.me SSL valid" false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Summary: ${PASS} passed, ${FAIL} failed ==="
|
||||||
|
if [[ $FAIL -gt 0 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user