Cloudflare in Front of Vercel: Next.js Caching Setup and Trade-offs

Next.js Cloudflare CDN in Front of Vercel: 2026 Caching Playbook

Here is the failure pattern we see most often. A route sets revalidate to 60 seconds on Vercel. A well-meaning Cloudflare Edge TTL of 4 hours sits in front of it. Users can now see HTML up to 4 hours and 1 minute old, and calling revalidatePath does nothing about it, because on-demand revalidation only purges Vercel's cache. That is the core problem with a Next.js Cloudflare CDN layer: you run two caches with two TTLs, and the invalidation signal reaches only one of them.

This 2026 update covers the full setup: DNS and proxy mode, cache rules for static, ISR and dynamic routes, and the header conflicts between the two edges. It also includes a staleness-budget model, a decision table for when to skip Cloudflare entirely, and a debugging procedure that tells you which edge served a response.

Cloudflare proxy layered in front of the Vercel edge network serving a Next.js application

How Does a Next.js Cloudflare CDN Layer Interact With the Vercel Edge Network?

Every proxied request passes through two tiers. The Cloudflare edge is the tier users see. The Vercel edge network, branded Vercel CDN since 2025, sits behind it as the second tier, and Vercel Functions handle rendering. Cloudflare treats Vercel as an origin. Vercel sees Cloudflare egress IPs as clients.

Three things changed for returning readers as of 2026:

  • Next.js 16 Cache Components. Caching is now explicit through cache directives and cacheLife profiles, and tag-based revalidation is more common. None of it propagates to Cloudflare.
  • Vercel's proxy stance. Vercel still discourages reverse proxies in front of its platform. A proxy hides client IPs from its firewall, bot detection and rate limiting. If you proxy anyway, you have to design around that trade-off.
  • Header targeting. Vercel now fully supports CDN-Cache-Control and Vercel-CDN-Cache-Control, which RFC 9213 made standard. This is how you give each edge a different TTL without conflicts.

How to Configure Cloudflare in Front of Vercel, Step by Step

DNS, TLS and Proxy Mode

  1. Add the domain in Vercel first and let it verify. Then create the record in Cloudflare. For subdomains, use a CNAME to cname.vercel-dns.com. For the apex, use Vercel's A record, 76.76.21.21. Set the record to proxied (orange cloud).
  2. Set SSL/TLS mode to Full (Strict). Flexible mode causes redirect loops against Vercel's HTTPS enforcement.
  3. Exempt the ACME challenge path under well-known from caching and WAF challenges. If you don't, Vercel's certificate renewals fail silently until expiry.
  4. Turn off Rocket Loader and HTML minification. Both rewrite the HTML, which can break hydration and cause checksum mismatches.
  5. In middleware, read the client IP from cf-connecting-ip. Vercel's geo and IP headers describe the Cloudflare edge location, which is usually near the user but not guaranteed to be.

Cache Rules for Static, ISR and Dynamic Routes

RouteCloudflare Cache RuleWhy
/_next/static/Eligible, Edge TTL 1 year, respect originContent-hashed and immutable
/_next/imageEligible, keep full query string in cache keyWidth and quality live in the query; ignoring it serves wrong sizes
ISR and cached HTMLEligible, Edge TTL "use cache-control header if present"Let targeted headers decide; never override
/api/, Server Actions, draft modeBypassPOST mutations and preview cookies
Authenticated SSRBypass when a session cookie is presentPrevents cross-user data leaks

Header Conflicts Between the Two Edges

This is the part most Next.js CDN caching guides get wrong. When Vercel sees plain Cache-Control with s-maxage, it consumes that header, then strips s-maxage and stale-while-revalidate before forwarding the response. Cloudflare therefore receives an ISR page marked public, max-age=0, must-revalidate. The result is DYNAMIC or a zero-TTL cache on Cloudflare, which is safe but pointless. Teams then add an Edge TTL override, and that override is what creates the stale-content incident described at the top.

Header set by the appVercel CDNCloudflare
Cache-Control with s-maxageHonors, then stripsSees max-age=0
Vercel-CDN-Cache-ControlHonors, consumesNever sees it
CDN-Cache-ControlHonors, forwardsHonors
Cloudflare-CDN-Cache-ControlPasses throughHonors, highest priority

The working pattern is to set Vercel-CDN-Cache-Control to your real revalidate window and Cloudflare-CDN-Cache-Control to a short TTL, typically 30 to 120 seconds. Cloudflare then absorbs request bursts while Vercel remains the source of truth for freshness.

The Staleness Budget

Worst-case staleness is roughly the Cloudflare TTL plus the Vercel revalidate window plus one render time. Write that number down for every route class. If the business can't tolerate it, you have two options. You can shorten the Cloudflare TTL, or you can wire revalidateTag calls to a Cloudflare purge-by-URL or purge-by-tag API call in the same handler. Deploys need the same hook, because Vercel purges its own cache on deploy and Cloudflare does not.

RSC Payloads and Vary

App Router serves HTML and React Server Component flight data from the same path, differentiated by the RSC and Next-Router-State-Tree request headers plus the _rsc query parameter. Cloudflare ignores Vary for non-image content. If a cache rule strips query strings from the cache key, a browser can receive raw flight data instead of a page. Keep the query string in the key for HTML routes.

Cloudflare in Front vs Vercel Edge Alone: Decision Table

SituationRecommendation
Mostly dynamic or personalized SSRVercel alone; a second hop adds latency with nothing to cache
Org already standardized on Cloudflare DNS, WAF and Zero TrustCloudflare in front with the targeted-header pattern
High Vercel data transfer bills from cacheable pagesCloudflare in front, short HTML TTLs, long static TTLs
Heavy media or downloadsSplit hostname: app on Vercel, assets on a volume-priced CDN
Heavy use of Vercel Firewall, BotID or geo middlewareVercel alone, or accept degraded client signals

The cost row deserves numbers. Per Vercel's 2025 published pricing, Pro includes 1 TB of Fast Data Transfer, and overage starts at $0.15 per GB in the cheapest regions. Check current regional rates, as 2026 figures may differ. At that rate, 100 TB a month of media overage runs about $15,000. Cloudflare's self-serve plans don't meter cached bandwidth, but their terms steer large video and file distribution toward paid products.

For the split-hostname pattern, the realistic comparison set is the volume-priced CDNs: Bunny.net, CDN77, Gcore and BlazingCDN. Bunny's storage and image tooling is mature, and it deserves a trial. BlazingCDN offers Anycast routing with built-in failover and flexible configuration. On BlazingCDN's volume pricing, that same 100 TB costs $415 a month, and rates fall to $2.50 per TB ($0.0025 per GB) at volume (as of October 2026). That spread matters for enterprise media workloads.

How to Debug Which Edge Served the Response

Send HEAD requests from your terminal's HTTP client and read four headers:

  • cf-cache-status: HIT, MISS, EXPIRED, REVALIDATED, BYPASS or DYNAMIC. DYNAMIC on an ISR route means Cloudflare never considered it cacheable.
  • age: Seconds since Cloudflare stored the response. If it exceeds your staleness budget, Cloudflare is the culprit.
  • x-vercel-cache: HIT, STALE, MISS, PRERENDER or REVALIDATED. It reflects Vercel's state at the time Cloudflare last fetched.
  • cf-ray and x-vercel-id: They identify the Cloudflare colo and the Vercel region. Mismatched geography explains odd TTFB.

To isolate Vercel, pin DNS resolution for the hostname to 76.76.21.21 in your client, or query the deployment's vercel.app URL. Compare the two responses. If Vercel returns fresh content and Cloudflare returns stale, purge Cloudflare and remove any Edge TTL override. A 403 that only appears when the request is proxied usually points to Vercel Deployment Protection or firewall rules challenging Cloudflare IPs.

Our 2026 operating targets are above 85% cache hit ratio on static routes, 50 to 70% on ISR HTML, and p95 TTFB under 200 ms on cached pages. Watch ISR revalidation invocations as well. A flatline means Cloudflare is absorbing everything. For emergency rollback, switch the record to DNS-only. Traffic goes straight to Vercel as soon as resolvers pick up the change.

FAQ

Does Vercel support running Cloudflare in front of it?

It works, but Vercel discourages it. The proxy hides real client IPs from Vercel's firewall, bot detection and rate limiting. If you proceed, use Full (Strict) TLS, read cf-connecting-ip in middleware, and avoid rules that challenge traffic Vercel sends back.

Why does Cloudflare show DYNAMIC for my ISR pages?

Vercel strips s-maxage from Cache-Control before forwarding the response, so Cloudflare sees max-age=0. Emit CDN-Cache-Control or Cloudflare-CDN-Cache-Control from the route and make the path eligible for cache.

How do I make Next.js CDN caching invalidate on both layers?

Call Cloudflare's purge API from the same handler that runs revalidatePath or revalidateTag. Add the same purge to your deploy pipeline. Otherwise Cloudflare serves its copy until its own TTL expires.

Is the Vercel edge network alone fast enough without Cloudflare?

For most Next.js apps, yes. It caches ISR and static output globally and understands framework semantics natively. Adding Cloudflare pays off mainly through consolidated security tooling or bandwidth cost savings, not raw latency.

Can Cloudflare cache the wrong content for App Router pages?

Yes, if the query string is dropped from the cache key. HTML and RSC flight data share a path. Keeping the _rsc parameter in the key prevents Cloudflare from serving flight data as a page.

Run This Check Before Friday

Pick your three highest-traffic ISR routes. For each one, record cf-cache-status, age and x-vercel-cache from two regions, trigger an on-demand revalidation, and repeat the requests. If age does not reset within your staleness budget, you have found the gap between your two caches. Then decide whether to shorten the Cloudflare TTL, wire in purges, or take Cloudflare off the HTML path entirely. We'd like to hear which one you chose, and what your purge latency looked like.

Heavy traffic.
Light bill.

The CDN for video and large traffic