web3icons

Caching SVG files with HTTP headers

How to cache SVG icon files with Cache-Control, immutable and stale-while-revalidate, content-hashed file names, the right content type and a CDN.

4 min read

Icons are close to the ideal case for HTTP caching: small files, requested again and again, that rarely change. With the right headers, a returning visitor loads your token list without a single icon request, and a CDN serves everyone else without touching your server. The main decision isn't which header to send, though. It's how your URLs behave when a file changes.

First question: does the URL change when the file does?

Every caching strategy starts here. Icon URLs come in two kinds.

Content-hashed URLs include a fingerprint of the file's contents, like /assets/eth.3f9a1c2b.svg. When the logo changes, the hash changes, so the new file has a new URL. Build tools generate these automatically for assets you import in code.

Stable URLs stay the same for the life of the file, like /icons/token/ETH.svg. You want these for icons people link to directly, for public icon libraries, and for logo URLs stored in a database or a token list.

With hashed URLs, you can tell browsers to keep a file forever, because any change will arrive under a new name. With stable URLs, a long lifetime means people keep seeing the old logo after a rebrand until their cached copy expires. So stable URLs need a shorter lifetime, revalidation, or both.

The directives that matter

Directive What it does
max-age=N The response is fresh for N seconds; no request is needed during that time
s-maxage=N Like max-age, but for shared caches such as CDNs, where it takes precedence
public Any cache may store the response, including shared ones
immutable The file won't change while fresh, so the browser shouldn't revalidate it, even on reload
stale-while-revalidate=N After expiry, a cache may serve the stale copy for up to N more seconds while it fetches a fresh one in the background
no-cache Store it, but check with the server before each use
no-store Don't store it at all

When a cached copy expires, the browser can revalidate it with a conditional request, using the ETag (If-None-Match) or the modification date (If-Modified-Since). If nothing changed, the server answers 304 Not Modified with no body. For a small icon that's nearly as cheap as a cache hit, but it still costs a round trip.

Support for immutable and stale-while-revalidate differs between browsers. Caches ignore directives they don't recognize, so including them is safe. Many CDNs also honor stale-while-revalidate; check your provider's documentation.

Header recipes

Hashed file names. Cache for a year and mark the file immutable. 31,536,000 seconds is 365 days.

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

Stable icon URLs. Pick a lifetime that matches how quickly a logo change has to show up. For example: a day in the browser, a week at the CDN, and a background refresh after expiry.

Cache-Control: public, max-age=86400, s-maxage=604800, stale-while-revalidate=86400

When you ship a corrected logo, purge it from the CDN rather than waiting out the week.

Versioned stable URLs. A middle path is adding a version to the query string, such as /icons/token/ETH.svg?v=2026-11, and changing it whenever the icon set updates. That lets you use long lifetimes on readable paths. Whether a CDN treats the query string as part of the cache key depends on its configuration, so check before you rely on it.

The HTML that references the icons. Give pages a short lifetime or no-cache, so a new deploy that points at new hashed files is picked up right away. If the whole site is pre-rendered, this split between short-lived pages and long-lived assets is the main thing to get right; see static generation for icon libraries.

Content type and compression

Serve SVG with Content-Type: image/svg+xml. Browsers don't infer SVG from a file's contents: served as text/plain or application/octet-stream, an SVG typically won't display in an <img>, and opened directly it may download or show as text. Web servers usually map the .svg extension correctly by default, but files uploaded to object storage sometimes get a generic type. Check the header rather than assuming.

SVG is text, so it compresses well. Make sure gzip or Brotli is enabled for image/svg+xml, since some servers only compress a short default list of types. The gain is largest for detailed logos of several kilobytes; for files under 1 KB it's small. Making the files themselves smaller usually helps more; see optimizing SVG files.

Setting the headers

In Next.js, you can set headers for files in public/ from next.config.ts:

import type { NextConfig } from 'next'

const config: NextConfig = {
  async headers() {
    return [
      {
        source: '/icons/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=86400, s-maxage=604800, stale-while-revalidate=86400',
          },
        ],
      },
    ]
  },
}

export default config

In nginx, whose default mime.types already maps .svg to image/svg+xml:

location /icons/ {
  add_header Cache-Control "public, max-age=86400, s-maxage=604800, stale-while-revalidate=86400";
  gzip on;
  gzip_types image/svg+xml;
}

Hosting platforms and CDNs offer the same settings through their own config files or dashboards.

Then check what's actually served. Send an Accept-Encoding header, or the response won't show compression:

curl -sI -H 'Accept-Encoding: br, gzip' https://example.com/icons/token/ETH.svg \
  | grep -iE '^(content-type|content-encoding|cache-control|etag|age):'

An Age header usually means the response came from a shared cache. Many CDNs also add their own header saying whether the request was a hit or a miss; run the command twice and compare.

A note on SVGs opened directly

An SVG opened directly in a browser tab, rather than through <img>, is a full document that can run scripts. That's not a concern for icons you drew yourself. If your library accepts uploads or proxies third-party logos, though, sanitize the files and send a restrictive Content-Security-Policy header with SVG responses. The guide on SVG security explains the risks and the headers.

More in Performance

All guides