Hardening del CDN per al mapa
El mapa de la web carrega .pmtiles (tiles vectorials via
range requests HTTP), glyphs .pbf i un pòster PNG des de
cdn.ediacarastudio.com/shared/maps/. Aquesta pàgina recull la configuració de
Cloudflare que ha d’existir abans de pujar les dades, o
el mapa es trenca (CORS) o cada visita costa lectures d’R2 (sense cache d’edge).
Per què cal
Section titled “Per què cal”R2 factura operacions de lectura (Class B), no l’egress (la sortida de dades és gratuïta). Una sessió de mapa ≈ 50 range requests / ~1,3 MB. Sense cache d’edge, cada visitant costaria ~50 lectures d’R2 per sempre (10 M/mes gratis, després $0,36/M). Amb la config de sota, les lectures d’R2 cauen a prop de zero independentment del trànsit.
Dos problemes detectats abans de pujar res:
| Símptoma | Causa |
|---|---|
.pmtiles no es cacheja (cf-cache-status: MISS) | L’extensió no és a la llista de cache per defecte de Cloudflare |
El fetch() del mapa falla | El bucket no tenia cap política CORS (ediacarastudio.com → cdn. és cross-origin) |
Les cinc peces
Section titled “Les cinc peces”1. Cache Rule — maps-cache-everything (crític)
Section titled “1. Cache Rule — maps-cache-everything (crític)”Caching → Cache Rules. Filtre: host cdn.ediacarastudio.com i path que comenci per
/shared/maps/. Acció: eligible for cache, Edge TTL i Browser TTL = 1 mes
(les dades són immutables; en actualitzar, canvia el nom del fitxer o purga la cache).
2. CORS del bucket (crític — el mapa no funciona sense això)
Section titled “2. CORS del bucket (crític — el mapa no funciona sense això)”R2 → bucket ediacarastudio-assets → Settings → CORS:
[ { "AllowedOrigins": [ "https://ediacarastudio.com", "https://www.ediacarastudio.com", "http://localhost:3000" ], "AllowedMethods": ["GET", "HEAD"], "AllowedHeaders": ["range", "if-match"], "ExposeHeaders": ["etag", "content-range", "content-length"], "MaxAgeSeconds": 86400 }]localhost:3000 manté el dev local funcionant contra el CDN real. Restringir
AllowedOrigins també impedeix que altres webs facin fetch() dels nostres tiles.
3. WAF — maps-block-foreign-referer (anti-hotlink)
Section titled “3. WAF — maps-block-foreign-referer (anti-hotlink)”Security → WAF → Custom rules. Bloqueja peticions a /shared/maps/ amb un Referer
que no contingui ediacarastudio.com (els referers buits es permeten a propòsit:
navegadors estrictes amb la privacitat els treuen). És dissuasió, no una frontera
de seguretat: el contains és laxa (un ediacarastudio.com.evil.tld passa); el
regex requeriria pla de pagament. El sostre de cost real el posen les regles 1 i 4.
4. Rate limiting — maps-rate-limit
Section titled “4. Rate limiting — maps-rate-limit”Security → WAF → Rate limiting. 150 peticions / 10 s per IP → block 10 s sobre
/shared/maps/. Una sessió legítima són ~50 peticions en pocs segons, així que hi ha
marge de sobra. ⚠️ El pla gratuït només permet una regla de rate-limit — és aquesta.
5. Bot Fight Mode
Section titled “5. Bot Fight Mode”Security → Bots → enable. Gratuït, bloqueja bots coneguts a tota la zona.
Pujada
Section titled “Pujada”La fa pnpm sync al repo geodata (scripts/sync.ts):
rclone copy (mai sync) limitat a shared/maps/, amb
Cache-Control: public, max-age=31536000, immutable. Fa servir copy perquè el
bucket és compartit amb els brand assets i sync els esborraria
(vegeu Cloudflare → R2).
Verificació (després de configurar + pujar)
Section titled “Verificació (després de configurar + pujar)”BASE="https://cdn.ediacarastudio.com/shared/maps"
# Cache d'edge: la 2a petició ha de ser HITcurl -sI "$BASE/camp-tarragona.pmtiles" | grep -i cf-cache-status # MISS, després HIT
# Range requests: 206 amb content-rangecurl -sI -r 0-16383 "$BASE/camp-tarragona.pmtiles" | grep -iE "HTTP|content-range"
# CORS: ha de tornar l'origen del sitecurl -sI -H "Origin: https://ediacarastudio.com" -r 0-127 \ "$BASE/camp-tarragona.pmtiles" | grep -i access-control-allow-origin
# WAF: referer aliè bloquejat (403)curl -s -o /dev/null -w "%{http_code}\n" -H "Referer: https://evil.example/" \ "$BASE/camp-tarragona.pmtiles" # 403