What Is Long Cache TTL? Complete Guide (2026)

Saar Twito9 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Is a Long Cache TTL?

Long Cache TTL (Time To Live) means setting a long Cache-Control: max-age on static assets so that repeat visitors don't re-download them on every visit. The recommended value for hashed static assets is one year — 31,536,000 seconds. Audits flag any static resource served with a TTL under 30 days because the user is paying a network cost they shouldn't have to.

Key Facts (TL;DR)

  • Recommended max-age: 31,536,000 seconds (1 year) for hashed JS, CSS, fonts, and images.
  • Audit threshold: resources with TTL < 30 days are flagged. Best score is at 1 year.
  • Use the immutable directive: Cache-Control: public, max-age=31536000, immutable tells the browser it never needs to revalidate — saving even the conditional request.
  • HTML is the exception: HTML should be short-cached (no-cache or 60–300 s) so users always fetch the version that points to the current asset hashes.
  • Repeat-visitor share: industry analysis shows repeat visitors typically make up 30–60% of traffic on established sites — that fraction benefits directly from long cache TTL.
  • Pattern: long cache + content hashing. Hashed filenames (main.a3f7c2.js) make cache invalidation automatic when content changes.

Think of caching like leaving your shoes by the front door. If they're right there, you can grab them instantly on the way out. If you have to fetch them from the closet every single time, every trip starts slower. A long cache TTL is the same idea: keep the assets the user already downloaded close by, and don't make them go get a fresh copy unless something has actually changed.

Why Long Cache TTL Matters for Performance

A short or missing cache TTL means every page view re-downloads CSS, JS, fonts, and images that haven't changed. The cost compounds across five places:

  • Time to First Byte (TTFB):additional asset requests delay the user's next render. With proper caching, repeat visits hit the browser cache and skip the network entirely for those files.
  • Bandwidth waste: on a 500 KB JS bundle, every cache miss costs the user 500 KB they paid for once already.
  • Mobile performance: repeat-visit speed-ups are most pronounced on slower mobile networks where every request roundtrip costs hundreds of milliseconds.
  • Server load: serving cacheable assets repeatedly burns origin CPU and bandwidth that could be saved by upstream caches.
  • Core Web Vitals:faster repeat-visit LCP and reduced INP both benefit when the browser doesn't need to re-acquire render-blocking resources.

The Pattern: Long Cache + Content Hashing

The reason a 1-year cache TTL is safe is because production builds emit asset filenames containing a content hash. When the file's contents change, the hash changes — and the URL becomes a brand-new resource the browser has never cached.

<!-- Today's build -->
<script src="/assets/main.a3f7c2.js"></script>

<!-- Tomorrow's build (after a code change) -->
<script src="/assets/main.b91d44.js"></script>

Because the URL itself changed, there is no "stale cache" problem — the browser simply has no copy of main.b91d44.js and fetches it fresh. The previous file (main.a3f7c2.js) sits unused in the cache until eviction; that costs nothing because the user never requests it again.

This is why the right Cache-Control header for hashed assets is the most aggressive one available:

Cache-Control: public, max-age=31536000, immutable
  • public — any cache (browser or shared) may store it.
  • max-age=31536000 — store it for one year.
  • immutable — promise the browser this URL will never serve different bytes, so it never needs to send a conditional revalidation request.

Why HTML Is Different

HTML should typically not have a long cache TTL. The HTML document is the entry point — it's where the hashed asset URLs are referenced. If you long-cache the HTML, users can be stuck pointing at old asset hashes that no longer exist on disk. Set HTML to Cache-Control: no-cache or a short TTL (60–300 seconds) so the browser revalidates the entry point on each visit while still long-caching the assets it references.

How to Check Your Site's Cache TTL

Cache TTL is set per response by the server (or origin behind a CDN). The fastest ways to audit it:

  • Greadme deep scan — surfaces every static asset with a TTL under the recommended threshold, names the offender, and pairs each finding with a one-click GitHub PR for the fix.
  • Greadme crawler scan — checks cache headers across every indexable page, so you can see whether the regression is local to one route (a misconfigured static folder) or sitewide.
  • Greadme AI visibility analyzer — connects performance findings (including cache regressions) to AI-search outcomes by scoring how well each page is positioned to be cited.
  • Chrome DevTools → Network panel — open a request, inspect the Response Headers, and look for Cache-Control. Any static asset under 30 days is a regression.
  • web.dev Measure— the "Uses efficient cache policy on static assets" audit lists every flagged resource with its current TTL.
  • Google Search Console → Crawl stats — indirectly visible through repeat-asset re-fetches by Googlebot when caching is too short.

8 Practical Ways to Set a Long Cache TTL

1. Cache Hashed Assets for One Year

Apply the strongest Cache-Control to anything with a content hash in the filename (JS bundles, CSS bundles, fonts, hashed images).

Cache-Control: public, max-age=31536000, immutable

2. Short-Cache HTML

HTML is the entry point — it must always reflect the current asset hashes. Set a short TTL or use no-cache to force revalidation on each visit.

Cache-Control: no-cache
# or
Cache-Control: public, max-age=300, must-revalidate

3. Configure Nginx for Static Folders

In Nginx, set headers per location block so hashed assets get the long TTL and HTML doesn't.

location ~* \.(js|css|woff2|png|jpg|svg)$ {
  expires 1y;
  add_header Cache-Control "public, max-age=31536000, immutable";
}

location / {
  expires -1;
  add_header Cache-Control "no-cache";
}

4. Configure Apache via .htaccess

The same idea using mod_expires and mod_headers:

<FilesMatch "\.(js|css|woff2|png|jpg|svg)$">
  ExpiresActive On
  ExpiresDefault "access plus 1 year"
  Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

5. Make Sure Your Build Emits Content Hashes

webpack, esbuild, vite, and most modern bundlers emit hashed filenames by default. If yours doesn't, enable it — long caching without hashing is unsafe.

6. Use immutable When You Can

immutable tells the browser the URL never serves different bytes, so it skips the conditional revalidation request entirely. Only use it on truly hashed (versioned) URLs.

7. Don't Set a Long TTL on Unhashed Assets

If a file is named main.jswith no hash, you cannot safely long-cache it — once it's in user caches you have no way to invalidate it. Either add a hash to the build output or use a short TTL.

8. Set Headers at the CDN Edge

If you're behind a CDN, the edge can override origin headers. Configure cache rules at the edge so hashed paths automatically receive the year-long TTL even if origin defaults are shorter.

Common Caching Mistakes and Fixes

Problem: Setting All Assets to Short TTL "to Be Safe"

What's happening: Out of fear of cache invalidation issues, every static asset is given a 1-day or 1-hour TTL. Repeat visitors re-download everything constantly.

Fix: Adopt content-hashed filenames in your build pipeline, then long-cache hashed assets at 1 year with immutable. Short caching is the wrong tool for the job.

Problem: Forgetting to Hash Filenames

What's happening: The build emits app.js with no hash, so the team is forced to use a short TTL — because there's no way to invalidate cached copies after a deploy.

Fix: Configure your bundler to output content-hashed filenames (e.g., app.[contenthash].js). Then safely raise the TTL.

Problem: Shipping the Same Hash Even When Content Changes

What's happening: A misconfigured build uses a deterministic but content-insensitive name. Files change, hash doesn't — caches serve stale code forever.

Fix: Switch from [name] to [contenthash] in the bundler output template. Verify hashes change after a deliberate file edit before relying on long caching.

Problem: HTML Is Long-Cached Alongside Assets

What's happening: A blanket cache rule applies max-age=31536000 to every response — including HTML. Users keep loading old HTML that points at deleted asset hashes, breaking the site.

Fix: Scope the long-TTL rule to file extensions or paths that contain hashed assets. Use no-cache or a short TTL for HTML.

How TTL Strategies Compare

Different resource types need different cache policies. Use this table as the default starting point.

Resource TypeRecommended TTLCache-Control HeaderWhy
Hashed JS / CSS bundles1 yearpublic, max-age=31536000, immutableURL changes whenever content changes — safe to cache forever.
Hashed images / fonts1 yearpublic, max-age=31536000, immutableSame as bundles — URL is the cache-busting mechanism.
Unhashed static assets1 hour – 1 daypublic, max-age=3600No way to force-invalidate; keep TTL short.
HTML documents0 – 5 minutesno-cache or max-age=300, must-revalidateEntry point — must reference current asset hashes.
API responses (JSON)Depends on data freshnessprivate, max-age=60 or no-storeOften user-specific or rapidly changing.
Service Worker file0no-cacheMust update immediately to ship new offline logic.

FAQ

What is the recommended Cache TTL for static assets?

One year — 31,536,000 seconds — combined with content-hashed filenames. The full recommended header is Cache-Control: public, max-age=31536000, immutable. Audits flag any static resource with a TTL under 30 days.

What does the immutable directive do?

immutable tells the browser the URL will never serve different bytes. With it set, the browser doesn't even send a conditional revalidation request — it just uses the cached copy until max-age expires. Use it only on URLs that change when content changes (i.e., hashed asset URLs).

Should HTML have a long cache TTL?

No. HTML is the entry point that references hashed asset URLs. If HTML is long-cached, users can get stuck pointing at deleted hashes. Use Cache-Control: no-cache or a short max-age (60–300 seconds) so the browser revalidates HTML on each visit.

How do I invalidate a long-cached asset if it's broken?

Change the URL. Because production assets are content-hashed, any code change automatically produces a new filename. Users will request the new URL on their next visit; old URLs sit unused in caches until eviction. There is no "purge" needed.

What's the difference between max-age, expires, and s-maxage?

max-age is the modern way to set TTL in seconds. Expires is the legacy header expressing an absolute date. s-maxage applies only to shared caches (CDNs, proxies) and overrides max-age there. Prefer max-age for browser caching and add s-maxage only when you need different shared-cache behaviour.

Does long cache TTL affect AI search engines like ChatGPT and Perplexity?

Indirectly. AI answer engines and Google AI Overviews preferentially cite pages that already rank well — and short-TTL repeat-visit penalties feed into Core Web Vitals and Page Experience scores. Faster repeat visits also mean Googlebot and AI crawlers pull less data per crawl, reducing crawl budget pressure on your origin.

Will long caching break my deploys?

Only if you long-cache without content-hashing. The combination of max-age=31536000, immutable + hashed filenames is safe by construction: every deploy emits new URLs the browser has never cached. The risk only appears if you long-cache unhashed filenames.

Conclusion

Long Cache TTL is one of the highest-ROI performance fixes available — it turns a 30–60% slice of your traffic (repeat visitors) into near-instant page loads, with no architectural changes. The pattern is simple: long-cache hashed assets with max-age=31536000, immutable, short-cache HTML so users always pick up the latest asset hashes, and let your bundler handle invalidation through content hashes automatically.

Run a Greadme deep scan to see exactly which assets on your site are missing the recommended TTL and ship a one-click fix.