Skip to main content
All fixes

Page speed, redirects, Core Web Vitals

Use a CDN to cut latency and offload origin traffic

Without a CDN, every user hits a single origin, paying full RTT and TLS setup cost. Push static assets to the edge with Cloudflare, Fastly, Vercel, or CloudFront.

What's happening

A CDN replicates your static (and increasingly dynamic) content to edge servers worldwide. Without one, every request from every user travels to your origin — typically a single region — paying the full round-trip latency, TLS setup, and origin processing cost. Users on the other side of the world can see TTFB north of 1 second on cold connections, even when your origin response time is 50ms.

Chrome DevTools' Network panel shows the Server header (sometimes) and the IP address (always) of the response. If every request hits the same origin IP, you're not on a CDN. WebPageTest's connection view exposes the resolved CDN provider, and tools like CDNCheck make it explicit.

Modern serverless platforms (Vercel, Netlify, Cloudflare Pages) include a CDN by default, so this issue is most common on legacy self-hosted setups, on bare-metal origins, and on small projects deployed directly to a single region of AWS, DigitalOcean, or Hetzner.

Why it matters

CDN absence directly inflates TTFB, which constrains LCP. A user in Sydney hitting a US-East origin pays 200-300ms RTT for the initial connection alone, plus TLS, plus origin processing. With a CDN, the same user gets the cached HTML from a Sydney edge in 30ms.

Origin load is the secondary issue. A CDN absorbs 95%+ of static asset traffic — every cache hit is a request your origin doesn't serve. For traffic spikes, viral moments, or DDoS-adjacent attacks, the CDN is the difference between staying up and going down.

Common causes

  • Self-hosted on a single VPS without a CDN in front.
  • DNS pointing directly at the origin IP instead of CDN-managed records.
  • Origin returns Cache-Control: no-store, defeating any CDN caching.
  • CDN cache rules misconfigured to bypass on cookies or query strings.
  • Image hosting on a custom subdomain not behind the CDN.
  • API responses cached too aggressively (or not at all) without thinking through invalidation.

Detect this on your site

Run a quick scan with the Speed Test. The tool surfaces this exact issue with the records and context needed to apply the fix below.

Open Speed Test

How to fix it

  1. 1

    Pick a CDN

    Cloudflare's free tier covers most small sites. Vercel and Netlify include CDN with hosting. Fastly is the choice for high-traffic with deep cache control. CloudFront if you're already on AWS. Bunny.net for cost-sensitive media-heavy sites.

  2. 2

    Point DNS at the CDN

    For Cloudflare, change nameservers to Cloudflare's. For others, create CNAME records pointing to the CDN's edge. Allow up to 48 hours for propagation, though most resolvers update within an hour.

  3. 3

    Set sane Cache-Control headers

    Static assets (JS, CSS, fonts, images): Cache-Control: public, max-age=31536000, immutable. HTML: Cache-Control: public, s-maxage=60, stale-while-revalidate=600 (cache at the edge, not the browser). API responses: case-by-case, often no-store unless idempotent.

  4. 4

    Verify cache hits in DevTools

    Cloudflare adds a CF-Cache-Status header (HIT, MISS, EXPIRED). Fastly adds X-Cache and X-Served-By. CloudFront adds X-Cache. After warming the cache (load the page once), subsequent requests should show HIT.

  5. 5

    Enable Brotli, HTTP/2, HTTP/3, and TLS 1.3 at the edge

    Most CDNs enable these by default. Verify in their dashboard — Cloudflare → Speed → Optimization, Fastly → Configuration, Vercel → Project settings. Each is a 100-300ms TTFB win on cold connections.

  6. 6

    Configure cache invalidation

    When you deploy new HTML, the CDN needs to know the cached copy is stale. Use immutable URLs (asset hashing) for static files and purge-on-deploy for HTML. Vercel and Netlify do this automatically; on Cloudflare use the API to purge specific paths.

  7. 7

    Add origin shield for high-traffic sites

    Origin shield is a single CDN POP that fronts your origin, absorbing cache-miss traffic from all other POPs. Reduces origin load on uncached or just-purged content. Cloudflare offers it via Argo, Fastly via Origin Shield, CloudFront natively.

Example

# vercel.json — per-route cache headers
{
  "headers": [
    {
      "source": "/_next/static/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    },
    {
      "source": "/(.*)\\.(jpg|jpeg|png|webp|avif|svg|woff2)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    },
    {
      "source": "/blog/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, s-maxage=60, stale-while-revalidate=600" }
      ]
    }
  ]
}

Vercel cache headers — long cache for hashed assets, SWR for HTML.

Frequently asked

For most sites under 100k monthly visitors yes. The free tier includes unlimited bandwidth basic caching DDoS protection and Brotli/HTTP/3. The Pro tier adds image optimization and advanced caching rules.

If you're already on Cloudflare/Vercel/Netlify use the built-in image CDN (Cloudflare Images Vercel Image Optimization). For self-hosted point image URLs at imgix or Bunny.net for transformation + edge caching in one.

Use immutable URLs (filename includes a content hash) for assets. For HTML use the CDN's purge API on deploy. Vercel/Netlify do this automatically; on Cloudflare call the purge endpoint with the changed paths.

Related fixes