Home  /  CDN Performance  /  Static Asset Optimization
CDN Performance

Static Asset CDN Optimization: CSS, JS, Fonts, and Image Delivery

By FlyFish NWA Editorial — Content Delivery Specialist September 8, 2026 9 min read

Static assets — CSS, JavaScript, fonts, and images — account for 80–90% of HTTP requests on modern web properties. Unlike dynamic HTML, these files are ideal candidates for edge caching, yet misconfigured headers, uncompressed payloads, and poor versioning leave 40–60% of potential performance gains untapped.

This guide provides a practical, asset-by-asset framework for optimizing static delivery through a CDN. You’ll learn how to configure cache control, compression, and protocols for each content type to reduce Time to First Byte (TTFB), Largest Contentful Paint (LCP), and Cumulative Layout Shift (CLS) without sacrificing cache hit ratio or deploy safety.

Key principle: Cache aggressively, version explicitly. Immutable assets with content hashes can be cached for a year at the edge and in the browser. Mutable assets must use short TTLs plus validation (ETag / Last-Modified) and fast cache invalidation workflows.

1. Why Static Assets Need a Dedicated CDN Strategy

Static assets are deterministic, highly cacheable, and bandwidth-intensive. A single unoptimized 280 KB render-blocking CSS file or a 1.2 MB hero image can dominate LCP. A CDN moves these bytes within 20–50 ms of users globally, offloads 85–95% of origin traffic, and enables protocol optimizations that origin servers rarely handle efficiently.

However, treating all static files identically creates problems. CSS and JS benefit from aggressive compression and HTTP/2 multiplexing. Fonts require CORS and careful display strategies. Images need content negotiation and responsive variants. A unified “cache everything for 1 hour” policy under-caches immutable files and over-caches mutable ones.

An effective strategy aligns with your CDN architecture: origin sets authoritative Cache-Control, the edge enforces tiered caching and compression, and the client leverages long-lived browser cache plus preload hints. When combined with proper SSL/TLS configuration, this reduces TLS negotiation overhead for every static request.

Static Asset CDN Delivery Flow Diagram showing origin to edge to browser flow with cache and optimization layers ORIGIN SERVER Build + Hash Cache-Control Brotli / Gzip app.a3f8c9.css PUSH / PULL CDN EDGE (PoP) Tiered Cache • Shield Compression • HTTP/2 & 3 Image Optimization HIT 92% MISS → Fetch STALE-WHILE ANYCAST BROWSER Memory • Disk Cache Preload • Preconnect font-display: swap LCP < 2.5s CLS < 0.1
Figure 1: Optimized static asset flow — hashed assets cached immutably at edge and browser; stale-while-revalidate masks origin latency.

2. CSS Delivery Optimization

CSS is render-blocking by default. Every kilobyte and millisecond counts toward First Contentful Paint (FCP).

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages

Minify and compress aggressively

Minify with cssnano or Lightning CSS, then pre-compress. Brotli at level 11 shrinks CSS 18–25% more than Gzip. Serve both encodings and let the CDN negotiate via Vary: Accept-Encoding. Cache compressed and uncompressed variants separately to avoid recompression at the edge.

Separate critical from non-critical CSS

Inline critical above-the-fold CSS (≤14 KB) and load the remainder asynchronously. Use media attributes or rel="preload" as="style" with an onload switch to prevent blocking:

<link rel="preload" href="/assets/app.a3f8c9.css" as="style">
<link rel="stylesheet" href="/assets/app.a3f8c9.css" media="print" onload="this.media='all'">

Cache strategy

Fingerprinted CSS (e.g., app.a3f8c9.css) should be immutable: Cache-Control: public, max-age=31536000, immutable. Non-fingerprinted files (e.g., /css/theme.css) should use max-age=3600, stale-while-revalidate=86400 with ETag validation. Purge via cache invalidation only for emergency fixes; prefer new filenames.

3. JavaScript Delivery Optimization

JavaScript impacts interactivity, Total Blocking Time (TBT), and bandwidth. CDN optimization focuses on delivery, not execution, but delivery choices directly affect parsing time.

# Nginx origin — immutable JS/CSS
location ~* \.(?:css|js)$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
  add_header Vary "Accept-Encoding";
  etag on;
  # Pre-compressed files: app.abc123.js.br / .gz
  gzip_static on;
  gunzip on;
}

Enable edge support for load balancing and failover so JS chunks are served from the nearest healthy PoP even during origin issues, using stale-while-revalidate to mask origin latency.

4. Font Delivery Optimization

Fonts are small but high-impact: a late font swap causes layout shift, a blocked font causes invisible text. CDN tuning must address CORS, caching, and display behavior.

Use WOFF2 only

WOFF2 is 30% smaller than WOFF and supported by >97% of browsers. Serve only WOFF2; let legacy browsers fall back to system fonts.

Subset aggressively

Subset to latin + required glyphs. A 180 KB full font often drops to 35–55 KB after subsetting. Use unicode-range to load language-specific subsets on demand.

Preload critical fonts

Preload 1–2 primary fonts: <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>. Do not preload more than two.

font-display: swap

Prevents FOIT (Flash of Invisible Text). Pair with size-adjust and fallback metrics to minimize CLS during swap.

Fonts require CORS. Set at origin or edge:

Access-Control-Allow-Origin: *
Cache-Control: public, max-age=31536000, immutable
Timing-Allow-Origin: *
/* Required for cross-origin font requests from CDN hostname */

Cache fonts immutably for one year. Because fonts rarely change without a filename change, a long TTL is safe and dramatically improves repeat-visit performance. Combine with DDoS protection rate-limiting to prevent font-scraping abuse from inflating bandwidth.

5. Image Delivery Optimization

Images typically represent 50–65% of page weight. CDN image optimization delivers the largest byte savings.

Modern formats and content negotiation

Serve AVIF and WebP with fallback to JPEG/PNG. AVIF reduces size 30–50% vs JPEG at equivalent quality; WebP reduces 25–35%. Use CDN content negotiation (Accept: image/avif,image/webp) or <picture> elements to serve the best format without URL changes.

Responsive variants

Generate 4–6 widths (320, 640, 960, 1280, 1920, 2560) and use srcset + sizes. The CDN should cache each variant separately and strip unnecessary metadata. Enable automatic quality selection (e.g., q=75–80 for photos) and chroma subsampling at the edge.

Cache and origin shielding

Images should be cached for 30–90 days with stale-while-revalidate. Use origin shielding to collapse requests for the same image variant to a single origin fetch. For user-generated images, normalize query parameters (e.g., ?w=800&h=600&fit=crop) to avoid cache fragmentation.

For performance budgeting, see our overview of CDN benefits for performance and security to correlate image weight reductions with Core Web Vitals improvements.

6. Cache Control and Versioning: The TTL Matrix

Versioning determines TTL safety. Content hashing (e.g., Webpack [contenthash], Vite hash) ties the filename to the file’s bytes. When the hash changes, the URL changes — making immutable caching safe. Without hashing, you must rely on validation and short TTLs.

Asset TypeVersioningCache-Control (Edge + Browser)Purge Strategy
CSS / JS (hashed)contenthash in filenamepublic, max-age=31536000, immutableNone — new filename on change
CSS / JS (unhashed)query string or nonepublic, max-age=3600, stale-while-revalidate=86400Surrogate-key purge on deploy
Fonts (WOFF2)hashedpublic, max-age=31536000, immutableNone
Images (static)hashed or versioned pathpublic, max-age=2592000, stale-while-revalidate=604800Path purge if needed
Images (UGC / dynamic)query params (w, h, format)public, max-age=86400, stale-while-revalidate=86400Normalize params; purge by tag

Always set Vary: Accept-Encoding for text assets and Vary: Accept for negotiated images. Avoid Vary: * and unnecessary Vary: User-Agent, which fragment cache. Strip Set-Cookie and Cookie from static asset responses at the edge — cookies prevent caching and inflate request headers.

7. Protocols, Compression, and Edge Tuning

HTTP/2 and HTTP/3

Enable HTTP/2 and HTTP/3 (QUIC) at the edge. Multiplexing eliminates head-of-line blocking for many small assets; QUIC improves lossy-network performance by 10–30% for mobile users. Ensure connection coalescing by serving all static assets from a single hostname (e.g., cdn.example.com) rather than sharding across cdn1, cdn2.

Compression

Pre-compress text assets at build time. Serve Brotli to capable clients, Gzip as fallback. Do not compress already-compressed formats (WOFF2, AVIF, WebP, JPEG, PNG) — it wastes CPU and can increase size.

TLS and early hints

Use TLS 1.3, OCSP stapling, and 0-RTT where safe. Send 103 Early Hints or Link: </assets/app.css>; rel=preload; as=style headers from the edge to initiate CSS/font fetches before HTML fully arrives. Pair with preconnect hints for third-party font providers if you cannot self-host.

8. Common Pitfalls to Avoid

Static asset optimization is iterative. Measure cache hit ratio (target >92% for static), edge TTFB (target <40 ms p50), and LCP before and after each change. Small, consistent wins — 15% smaller CSS, one fewer font, AVIF images — compound into sub-second loads globally.

Frequently Asked Questions

What cache TTL should I use for versioned static assets?

For fingerprinted or content-hashed assets (e.g., app.a3f8c9.css), use immutable caching: Cache-Control: public, max-age=31536000, immutable. This allows 1-year edge and browser caching. Never use immutable without a content hash, as updates will not propagate without a filename change.

Should fonts be served from the same CDN as CSS and JS?

Yes, serving fonts from the same CDN hostname avoids extra DNS and TLS handshakes and improves HTTP/2 connection coalescing. Ensure CORS headers (Access-Control-Allow-Origin: *) are set for cross-origin font requests and use font-display: swap to prevent invisible text.

How does Brotli compare to Gzip for static assets?

Brotli (br) typically reduces text asset size by 15-25% over Gzip at similar decompression speeds. Pre-compress CSS and JS at Brotli level 11 and Gzip level 9 at build time, then serve with content negotiation. Enable CDN support for both encodings and cache variants separately via Vary: Accept-Encoding.

Why are my images not hitting the CDN cache?

Common causes are missing Cache-Control headers from origin, query-string cache busting without normalization, Vary headers that fragment cache keys, or Set-Cookie on static responses. Strip cookies at the edge for static paths, normalize query strings, and set explicit Cache-Control from origin or via edge rules.

References & Standards: Cloudflare documentation · RFC 7234