HTTP Caching Strategies
Leverage HTTP caching to improve performance and reduce bandwidth.
By EMEPublished: February 20, 2025
httpcachingperformancecdnheaders
A Simple Analogy
HTTP caching is like keeping frequently used items on your desk. You don't reacquire them from storage; you just grab them.
Why Caching?
- Speed: Instant responses
- Bandwidth: Reduce network traffic
- Server load: Fewer requests
- User experience: Faster page loads
- Reliability: Works offline
Cache Headers
// Set cache policy
app.MapGet("/api/products/{id}", async (int id) =>
{
var product = await GetProductAsync(id);
return Results.Ok(product);
})
.WithName("GetProduct")
.WithOpenApi()
.CacheOutput(policy => policy
.Expire(TimeSpan.FromHours(1))
.Tag("products")
);
// Or manual headers
app.MapGet("/static/file", () =>
{
return Results.File("file.txt", "text/plain")
.WithHeaders(h =>
{
h.CacheControl = "public, max-age=3600";
h.ETag = "\"abc123\"";
h.Expires = DateTimeOffset.UtcNow.AddHours(1);
});
});
Cache-Control Directives
// Public cache (CDNs and browsers)
Cache-Control: public, max-age=3600
// Private cache (browser only)
Cache-Control: private, max-age=3600
// No caching
Cache-Control: no-cache, no-store
// Revalidate before using
Cache-Control: public, max-age=3600, must-revalidate
Example in code:
response.Headers.CacheControl = "public, max-age=3600, must-revalidate";
ETags and Validation
app.MapGet("/api/data", async (HttpContext context) =>
{
var data = await GetDataAsync();
var etag = "\"data-v123\"";
// Client has fresh copy?
if (context.Request.Headers.IfNoneMatch == etag)
{
return Results.StatusCode(304); // Not Modified
}
context.Response.Headers.ETag = etag;
context.Response.Headers.CacheControl = "max-age=3600";
return Results.Ok(data);
});
Conditional Requests
// Last-Modified
app.MapGet("/file", async (HttpContext context) =>
{
var lastModified = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
if (context.Request.Headers.IfModifiedSince.Count > 0)
{
var clientTime = DateTimeOffset.Parse(context.Request.Headers.IfModifiedSince);
if (clientTime >= lastModified)
{
return Results.StatusCode(304); // Not Modified
}
}
var file = await GetFileAsync();
context.Response.Headers.LastModified = lastModified.ToString("r");
context.Response.Headers.CacheControl = "max-age=86400";
return Results.File(file, "application/octet-stream");
});
Distributed Caching
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
app.MapGet("/api/expensive", async (
IDistributedCache cache,
HttpContext context) =>
{
const string cacheKey = "expensive-data";
// Try cache first
var cachedData = await cache.GetAsync(cacheKey);
if (cachedData != null)
{
return Results.Ok(System.Text.Encoding.UTF8.GetString(cachedData));
}
// Compute if not cached
var data = await ExpensiveComputationAsync();
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
// Cache for 1 hour
await cache.SetAsync(cacheKey, bytes,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
});
return Results.Ok(data);
});
Best Practices
- Version assets: Use query params or filenames
- Cache busting: Change URLs for new content
- Validation: Use ETags and Last-Modified
- Security: Cache-Control for sensitive data
- Monitoring: Track cache hit rates
Related Concepts
- CDN strategies
- Service workers
- Cache invalidation
- Browser storage (localStorage, sessionStorage)
Summary
HTTP caching dramatically improves performance through intelligent reuse of responses. Use Cache-Control headers, ETags, and distributed caches for optimal results.