NNicheStackTutorials
← All tutorials
Static Hosting

Cache-Control on Vercel Static Sites Without a Backend

Lighthouse wanted longer cache TTL. vercel.json headers cut repeat-load time 60% and fixed stale HTML after deploys.

·10 min read·Static Hosting
Cache-Control on Vercel Static Sites Without a Backend cover

Site: Pure static VitePress export on Vercel Lighthouse: "Serve static assets with efficient cache policy" — score 72

Default Vercel behavior

HTML often gets short cache. Hashed JS/CSS from Vite get immutable automatically if filename has content hash.

Unhashed /assets/logo.png cached 0s by default — repeat visitors re-fetch.

vercel.json headers

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Content-Type-Options", "value": "nosniff" }
      ]
    },
    {
      "source": "/assets/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    },
    {
      "source": "/((?!assets/).*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
      ]
    }
  ]
}

HTML: always revalidate (fresh content after deploy). Hashed assets: cache 1 year.

Stale HTML problem I caused

First version cached HTML for 1 year:

"value": "public, max-age=31536000"

Deployed new post — users saw old homepage for hours. Never long-cache HTML on content sites.

Verify headers

curl -I https://yoursite.com/assets/app.a8f3b2.js
# cache-control: public, max-age=31536000, immutable

curl -I https://yoursite.com/
# cache-control: public, max-age=0, must-revalidate

CDN invalidation

Vercel purges edge cache on each deploy for changed files. No manual purge needed for static.

Stale asset edge case

If you deploy without filename hashing, bump a query string in template as emergency fix:

<link rel="stylesheet" href="/assets/main.css?v=20260506">

Not a long-term solution — enable Vite/Webpack content hashes instead.

Security headers bonus

Same vercel.json can set HSTS once you're confident in HTTPS:

{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" }

Only add after HTTPS works everywhere — including www and apex.

Result

Repeat visit LCP: 2.1s → 0.9s. Lighthouse cache audit: pass.

Tags: VercelCache-ControlPerformanceStatic Site