Skip to content

ADR-0003: Migrate huskydata.io from AWS S3 + CloudFront to Cloudflare Pages

Context

The initial website (pre-launch) was hosted on AWS S3 with CloudFront CDN. For a static React/Vite SPA with modest traffic expectations, this stack was: - Costly relative to traffic (CloudFront data transfer + S3 ops add up even at low volume) - Operationally heavy: separate invalidation cycle for CloudFront cache on deploys - Cognitively split: AWS Console for hosting, but DNS/email elsewhere, TLS managed via ACM with its own renewal ceremony - Limited in developer UX — deploys via aws s3 sync scripts, no PR preview, no automatic rollback

Husky Data's traffic profile: - Pre-launch: low, bursty, international (APAC-weighted) - Post-launch: expected moderate growth, still geographically skewed to APAC - Static site — no server-side rendering needed

Cloudflare advantages were already latent: the company's other infrastructure (n8n domain, DNS, email forwarding) was either on or moving to CF. Consolidating reduces cognitive and operational surface.

Decision

Migrate the website to Cloudflare Pages. Switch authoritative nameservers from AWS Route 53 to Cloudflare. Keep email (MX records, SPF, autodiscover) preserved through migration. Use wrangler CLI for deploys, with GitHub Actions as long-term CI.

Specifics: - Custom domain: huskydata.io apex + www redirect - Build: npm run build/dist - PR preview deploys automatic via *.pages.dev - Security headers via public/_headers (CSP, HSTS, X-Frame-Options, Permissions-Policy) - Redirects via public/_redirects (SPA fallback + 301s for legacy URLs) - AWS S3 + CloudFront + Route 53 retired post-migration

Alternatives considered

  • Stay on AWS S3 + CloudFront — known-good, team familiar with AWS. Rejected: cost, ops friction, no PR previews, DNS/TLS ceremony.
  • Vercel — excellent DX, generous free tier, great for Vite. Rejected: pricing is user-seat-based and can grow unexpectedly at team scale; CF lines up better with our other infra.
  • Netlify — mature, good DX. Rejected: same reasoning as Vercel, plus smaller APAC edge presence.
  • Cloudflare Pages (chosen) — free tier covers our volume forever, CF edge is strong in APAC, consolidates with n8n/DNS/Access, security headers native.

Consequences

What gets easier

  • Single dashboard for DNS, Pages, Access, email routing (post-migration)
  • PR preview URLs automatic (<branch>.huskydata-io.pages.dev)
  • Zero-config TLS, auto-renewed
  • Security headers shipped with the app (_headers file)
  • Deploy via wrangler pages deploy — one command
  • Cloudflare Access gates all subdomain-scoped private sites (wiki, etc.)

What gets harder / what we give up

  • Vendor concentration risk — if CF has an outage, website + n8n + wiki all affected
  • Less granular AWS-style IAM — CF tokens are coarser-grained
  • AWS WAF ruleset (if we had built one) doesn't port — CF's WAF is different

What this commits us to (reversibility cost)

  • Low. DNS flip back to AWS is same ceremony; build artifacts are portable (static HTML); bucket recreation is trivial
  • The dependency on wrangler.toml + _headers / _redirects files is CF-specific but replaceable

Risks & mitigations

  • Cloudflare outage affects multiple services — mitigation: document runbook for DNS failover; consider status page monitoring
  • Build environment differs from local (Node version, pip availability) — mitigation: explicit NODE_VERSION in CF Pages settings; wiki build adds Python via pip install
  • CSP + inline analytics script tension — mitigation: explicit allowlist for googletagmanager + cloudflareinsights in _headers

Review

  • Next review: 2027-04-18 (annual)
  • Triggers to revisit early:
  • Cloudflare pricing change that makes free/paid tier economics flip
  • Repeated global CF outages (>2 per year)
  • Need for capabilities CF doesn't offer (serverless Postgres, specific AWS-only services)