Using Cloudflare Pages doesn't automatically use Cloudflare's CDN, even if you enable the proxy.
In this post, I'll walk you through exactly how we set up comprehensive caching that makes our marketing site lightning fast, serving most content directly from Cloudflare's global edge network.
Why Caching Matters for Performance#
Before diving into the implementation, let's understand why caching is so crucial:
- Reduced Server Load: Cached content is served directly from CDN edge servers, reducing load on your origin server
- Lower Latency: Content is served from the nearest edge location to your users, dramatically reducing response times
- Better User Experience: Faster page loads lead to better engagement and conversion rates
- Cost Savings: Less bandwidth usage on your origin server means lower hosting costs
- Improved SEO: Google considers page speed as a ranking factor
The Challenge with Static Sites#
Even static sites need smart caching strategies. Different types of assets have different caching requirements:
- Static Assets (images, CSS, JS): Can be cached for long periods since they rarely change
- HTML Pages: Need shorter cache times to ensure content freshness
- API Responses: Should have minimal caching to ensure data accuracy
- Dynamic Content: May need real-time updates
The key is finding the right balance between performance and freshness.
Our Cloudflare Caching Strategy#
At Userbird, we implemented a two-pronged approach using both _headers and wrangler.toml files to ensure comprehensive caching across our entire site.
Cloudflare Pages automatically respects _headers files placed in your site root. Here's our configuration:
X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() # Cache static assets for 1 year /*.js Cache-Control: public, max-age=31536000, immutable /*.css Cache-Control: public, max-age=31536000, immutable /*.woff2 Cache-Control: public, max-age=31536000, immutable # Cache images for 1 year /*.png Cache-Control: public, max-age=31536000, immutable /*.webp Cache-Control: public, max-age=31536000, immutable /*.avif Cache-Control: public, max-age=31536000, immutable # Cache HTML pages for 1 hour /*.html Cache-Control: public, max-age=3600, s-maxage=86400 # Cache API responses for 5 minutes /api/* Cache-Control: public, max-age=300, s-maxage=36002. The wrangler.toml Configuration
For more advanced control, we also use a wrangler.toml file that provides additional caching directives and build configuration:
[build] command = "npm run build" cwd = "./" watch_dir = "src" # Static asset caching rules [[headers]] name = "Cache-Control" value = "public, max-age=31536000, immutable" includes = ["*.js", "*.css", "*.woff2", "*.png", "*.jpg", "*.webp", "*.avif"] # Cache HTML with shorter duration [[headers]] name = "Cache-Control" value = "public, max-age=3600, s-maxage=86400" includes = ["*.html", "/"] ```Understanding Cache-Control Directives#
Let's break down the key Cache-Control directives we use:
- public: Content can be cached by any cache (browsers, CDNs, proxies)
- max-age=31536000: Browser cache expires after 1 year (31,536,000 seconds)
- s-maxage=86400: Shared cache (CDN) expires after 24 hours, even if browser cache is longer
- immutable: Tells browsers the content will never change during its cache lifetime
Why Different Cache Times?
Static Assets (1 Year): CSS, JS, images, and fonts rarely change. When they do, we use cache-busting techniques (filename hashing) to force updates.
HTML Pages (1 Hour): We want fresh content for users, but can tolerate some staleness for performance. The s-maxage=86400 means CDN caches for 24 hours.
API Responses (5 Minutes): Dynamic data needs to be relatively fresh, but brief caching still provides performance benefits.
Our caching configuration also includes essential security headers:
- X-Frame-Options: DENY: Prevents clickjacking attacks
- X-Content-Type-Options: nosniff: Prevents MIME type sniffing
- Referrer-Policy: strict-origin-when-cross-origin: Controls referrer information
- Permissions-Policy: Restricts access to sensitive browser features
These headers are automatically applied to all responses, improving both performance and security.
The Results: Dramatic Performance Improvements#
After implementing our comprehensive caching strategy, we saw remarkable improvements:
- 90% reduction in origin server requests
- 60% faster average page load times
- 95th percentile response times under 200ms globally
- 99% cache hit ratio for static assets
- Lighthouse performance score improved from 85 to 98
Most importantly, our users notice the difference. Pages load instantly, and our bounce rate decreased significantly.
Implementation Tips and Best Practices#
1. Use Versioned Assets
Always use cache-busting for static assets. Tools like Next.js, Webpack, or Vite automatically add hashes to filenames:
main.js → main.a1b2c3d4.js styles.css → styles.e5f6g7h8.css
This allows for aggressive caching (1 year) while ensuring updates are immediately available.
2. Monitor Cache Performance
Use Cloudflare Analytics to monitor: - Cache hit ratios - Origin server requests - Bandwidth savings - Performance improvements
3. Test Thoroughly
Always test your caching configuration: - Use browser dev tools to verify cache headers - Test with different file types - Verify that updates are properly deployed - Check cache behavior across different regions
4. Plan for Cache Invalidation
Have a strategy for when you need to invalidate cache: - Use Cloudflare's purge cache API - Implement cache tags for granular control - Consider using shorter cache times for frequently updated content
Common Pitfalls to Avoid#
Don't Cache Dynamic Content Aggressively
Be careful with API responses and user-specific content. Too much caching can lead to stale data and poor user experience.
Watch Out for Mobile vs Desktop
Ensure your caching strategy works well for both mobile and desktop users. Mobile users especially benefit from aggressive caching due to potentially slower connections.
Remember Time Zones and Global Users
With a global CDN, consider how cache expiration times affect users in different time zones. Use UTC for all cache-related timestamps.
Conclusion#
Implementing comprehensive caching on Cloudflare's CDN is one of the highest-impact optimizations you can make for your website. The combination of _headers and wrangler.toml configuration gives you fine-grained control over how different types of content are cached.
At Userbird, this caching strategy has been crucial for maintaining excellent performance as we scale. Our users get lightning-fast experiences, our servers handle less load, and we save on bandwidth costs - it's a win-win-win.
The key is to be aggressive with static assets, conservative with dynamic content, and always monitor your cache performance to ensure you're getting the benefits without compromising user experience.
Ready to speed up your site? Start with the configuration we've shared here, adapt it to your specific needs, and watch your performance metrics soar. Your users (and your search rankings) will thank you.
Ready to Try Privacy-First Analytics?
See how Userbird can give you powerful insights without compromising user privacy. No cookies, no tracking consent needed, fully GDPR compliant.