Choice 07. By Nicolas Dao

Cloudflare or S3 and CloudFront for a website?

Where should a website, SPA or PWA live, Cloudflare static hosting or S3 behind CloudFront, and where do its heavy files go?

Cloudflare static hosting by default: push to main and it's live, with static requests free and unlimited. S3 and CloudFront when the client's policy keeps every component inside their AWS accounts, or the origins must stay private in AWS. Often both: the site on Cloudflare and its API on AWS, or S3 in Sydney as the system of record with R2 serving the public copy.

When to pick which

Pick Cloudflare static hosting when

  • Marketing sites, docs, landing pages, SPAs and PWAs that should never need a server.
  • A static front end whose API lives elsewhere: a Lambda function URL in Sydney, or a Worker on the same domain.
  • Sites that get sudden spikes: static requests aren't metered, so a viral day costs the same as a quiet one.
  • Some pages must render on request: Astro or Next.js on Workers keep the static part free and run code only for those routes.

Pick S3 and CloudFront when

  • The client's policy says every component runs inside their AWS accounts.
  • Origins that must stay private inside AWS: a private bucket through origin access control, an internal load balancer through VPC origins.
  • Paid or private files from S3: signed URLs or signed cookies, checked at the edge.
  • A public site under 50 TB and 125M requests a month where the client wants one AWS bill: a flat-rate plan caps it.

Pick R2 for the files when

  • Media, downloads and exports served publicly at volume: R2 charges $0 egress where S3 charges $0.114/GB from Sydney.
  • Files over 25 MiB, which static hosting won't take.

Both, when

  • The site on Cloudflare static hosting, its API and data on AWS: Cloudflare DNS for the front door, Route 53 for the API and internal names, a subdomain delegated with NS records.
  • S3 in Sydney as the private, versioned system of record and R2 as the public delivery copy, filled lazily by Sippy so AWS egress is paid once per object.
  • An all-AWS site on pay-as-you-go CloudFront with Cloudflare in front, when a viral page would otherwise become the bill.

Questions that change the answer

  • Must every component run inside the client's AWS accounts? Then it's S3 and CloudFront, whatever the site is.

  • Must the files stay in Australia? R2 has no Australian jurisdiction, only a best-effort Oceania hint: use S3 in Sydney.

  • How much public traffic, in TB and requests a month? Above 50 TB or 125M requests, a CloudFront flat-rate plan no longer covers it.

  • Does any page render per request from live data? Then it's Astro or Next.js on Workers, not plain static hosting.

  • Will anyone technical run it? If not, Vercel or Netlify beat a complicated AWS setup.

The short version

Cloudflare static hosting S3 + CloudFront
Verdict My default for websites S3 my default for storage, CloudFront use when it fits
Deploy Push to main, live worldwide Bucket, origin access control, cache policies, certificate, invalidations
Static requests Free and unlimited $0.0125 per 10,000 HTTPS requests after 10M free (pay-as-you-go)
Egress to Australia $0 First 1 TB free, then $0.114/GB
Flat-rate option Not needed Pro $15 (10M requests, 50 TB), Business $200 (125M requests, 50 TB)
Certificate None to assemble ACM in us-east-1, even for a Sydney app
Limits per site 25 MiB per file; 20,000 files Free, 100,000 Paid Large public files at volume: egress dominates
Heavy media R2 R2, rather than inside a flat-rate plan
Private AWS origins Not its job: a Worker in front of AWS Origin access control, VPC origins
Paid or private files A Worker signs the link to R2 Signed URLs or signed cookies
DNS Cloudflare DNS, proxied Route 53 alias at the apex, free queries
IaC @pulumi/cloudflare, Wrangler does the upload @pulumi/aws, plus aws-native for the flat-rate plan

Price only decides this at volume. On pay-as-you-go, 20 TB a month from Sydney is about $2,158 on S3 and CloudFront against about $44 on R2: that's prohibitive. Below that, what decides it is assembly and where the client says the site must live.

Why Cloudflare static hosting is my default

Push to main, and it's live worldwide. I've had nothing to fix. There's no bucket, certificate or cache invalidation to assemble, and a launch, a viral post or a bot crawl costs nothing, because static requests aren't metered.

"Static" goes further than people think. A React or Vue SPA, a PWA, a docs site and a marketing site are all files. Put the API somewhere else, a Lambda function URL in Sydney or a Worker on the same domain, and the front end never needs a server. When some pages need rendering on request, I'd reach for Astro on Workers for a content site and Next.js on Workers (OpenNext) for a Next.js app. Both keep the static files free.

For a new site I'd start on Workers with static assets, not Pages: in April 2025 Cloudflare said new projects should start on Workers, and Pages gets no new investment. Working Pages projects can stay where they are.

When S3 and CloudFront win

CloudFront is the front door of an app that lives in AWS. It reaches private buckets through origin access control and internal load balancers through VPC origins, and it signs URLs for paid content. Transfer from S3 to CloudFront is free, so files served from S3 go through CloudFront, never straight out of the bucket.

My complaint was always egress. The flat-rate plans changed that: Business is $200 a month per distribution for 125M requests and 50 TB, with the WAF and DDoS protection included and no overage charge. For a client who wants one AWS bill, that's now a fair deal. Two catches: sustained use above the allowance gets your traffic served from fewer, more distant edge locations, and real-time logs, staging distributions and multi-tenant distributions keep you on pay-as-you-go.

This site is one of those: it runs on S3 and CloudFront in our own AWS account. It shows what "more assembly" means: an S3 origin behind origin access control, a certificate in us-east-1, 403 and 404 mapped to a real 404 page (S3 answers 403 for a missing file behind origin access control), DNS records in a Route 53 zone in a shared account, and a GitHub Actions deploy that exchanges an OIDC token for a role allowed to sync one bucket and invalidate one distribution. All of it in Pulumi. On Cloudflare, most of that list doesn't exist.

R2 or S3 for the files

Cloudflare R2 Amazon S3 (Sydney)
Storage $0.015/GB-month $0.025/GB-month (Standard)
Writes $4.50 per million $5.50 per million PUT/LIST
Reads $0.36 per million $0.44 per million GET
Internet egress $0 $0.114/GB (first 10 TB)
20 TB served a month, 2 TB stored About $44 About $2,158 with CloudFront, about $228 on the Business flat-rate plan
Versioning, replication, Object Lock No Yes
Archive classes Infrequent Access only Standard-IA, Glacier Instant Retrieval, Intelligent-Tiering
Access control API tokens scoped to buckets IAM roles, bucket policies
Australian residency No: a best-effort Oceania hint Yes: the region is a guarantee
Read by a Worker A binding, no keys Signed requests

R2 is for public files at volume: video, downloads, installers, model weights, exports that outside parties pull. It doesn't replace S3, and I wouldn't try. Data lakes stay in S3: Athena and Glue expect it, and compute in a private subnet reaches R2 through a NAT gateway, so every byte written to R2 is AWS egress.

The pattern is side by side. S3 in Sydney stays the system of record: private, versioned, in the lake. R2 holds the public delivery copy on a custom domain behind Cloudflare's cache, filled from S3 by Sippy as requests arrive. Turn off the r2.dev address as soon as the custom domain is live: it's rate-limited, has no cache, and leaves the files public around your WAF rules.

Vercel and Netlify: skip, unless nobody technical runs it

Vercel and Netlify are excellent. Their real product is one workflow: push, preview, ship, roll back, on one dashboard. My stack has no such thing; I wire previews, rollbacks and environments together with Pulumi and GitHub Actions, and that takes real engineering. I skip them because my clients are enterprises: most of their work is in the backend, and I need all of AWS behind it. If you aren't technical, pick one of them over a complicated AWS setup.

Traps

  • One zone, one provider. Cloudflare's proxy with Route 53 kept authoritative is only on Business and Enterprise; on Free and Pro, Cloudflare must hold the nameservers. Delegate subdomains with NS records rather than splitting records by hand (DNS).
  • Previews are public. Cloudflare Pages preview deployments have guessable URLs by default. Turn on the access policy, or Cloudflare Access on the preview hostnames.
  • SPA deep links. On Cloudflare, set not_found_handling = "single-page-application". On CloudFront with origin access control, map 403 and 404 to /index.html with a 200 for an SPA, or grant s3:ListBucket so a normal site returns real 404s.
  • Invalidations. The first 1,000 paths a month are free, then $0.005 per path. Hash asset names and invalidate only the HTML, or send one /*.
  • Moving DNS with DNSSEC on breaks the domain. Remove the DS record at the registrar first, wait, switch nameservers, then re-enable it.

Sources, checked on 25 October 2026: Workers static assets: billing and limitations, frontend, backend and database in one Worker (Cloudflare, April 2025), single-page application routing, Pages limits, CloudFront flat-rate plans, CloudFront pay-as-you-go pricing, flat-rate allowances and unsupported features, S3 pricing, R2 pricing, R2 limits, R2 data location, OpenNext Cloudflare adapter, Astro Cloudflare adapter, Route 53 pricing, Cloudflare DNS proxy status.

More choices

  1. 01 Lambda, Web Functions or Workers? Where should this piece of logic run?
  2. 02 Lambda, Fargate or EC2? My Lambda is busy all day, or its reserved concurrency keeps going up. When is it time to move to a container, and where is the line?
  3. 03 SQS, EventBridge or SNS? Something happens in my app and other code must act on it. Do I put it in a queue, on an event bus, or on a topic?
  4. 04 SQS retries or Durable Functions? A job calls something flaky (a third-party API, an LLM, a slow Postgres). How do I retry it reliably, and when do I reach for Lambda durable functions instead of tuning SQS?
  5. 05 pgvector or S3 Vectors? Where should my embeddings live?
  6. 06 Pulumi or Terraform? We're putting our infrastructure in code. Pulumi or Terraform, and what if we already run one of them?
  7. 08 CloudWatch and Firehose, side by side How do I keep chatty logs without a CloudWatch-sized bill?
  8. 09 Athena or DuckDB? How should I query my data lake, and what changed when AWS bought DuckDB's company?
  9. 10 Neon or Aurora? I need Postgres for an app on AWS. When is Neon the right call, and when does it have to be Aurora?
  10. 11 Better Auth or Cognito? Which sign-in should my app use, and what do I actually get for free?
  11. 12 Why I ended up on AWS Which cloud should my company run on, and is AWS just the one you know?
  12. 13 Better Auth, run yourself or managed by Neon? My app is on Neon. Should I run Better Auth myself, or switch on Neon Auth, which is Better Auth managed by Neon?
  13. 14 Stripe or PayPal Should we take payments with Stripe or PayPal, and when is PayPal worth the pain?
  14. 15 Tailscale or Cloudflare Tunnel? I need to reach a private box or VPC, or put a service from it online. Is that Tailscale or Cloudflare Tunnel?
  15. 16 Resend or SES? Our app sends email, and maybe a newsletter. Resend, or Amazon SES in our own AWS account?
  16. 17 WhatsApp or Telegram Our bot should reach users in a messaging app. WhatsApp or Telegram, and why did yours get blocked?
  17. 18 Which model, at what price Claude, GPT or an open model? Which is the cheapest model that's good enough, tier by tier?
  18. 19 Function URL or API Gateway? My Lambda needs an HTTPS endpoint. When is a function URL enough, and when do I start on API Gateway from day one?

Want this stack in your AWS account?

Cloudless Labs is a Sydney consultancy. We migrate on-premises systems to AWS, build serverless apps on this stack, and coach teams to ship with Claude Code and Codex.

Contact Us

For general inquiries, please contact us at:

For e-learning capabilities, please contact: