MapStaticAssets: Static Files That Actually Cache Properly
UseStaticFiles() has been the default for serving CSS, JS, and images since ASP.NET Core 1.0. It works. But it’s dumb: no content-based ETags, no precompressed variants, no fingerprinted cache headers. You end up with stale CSS in your users’ browsers, or you manually configure response caching middleware, or you append ?v=2 query strings like it’s 2010.
.NET 9’s MapStaticAssets() fixes all of this with one line.
The Swap
// Before: basic file serving, no smart cachingapp.UseStaticFiles();
// After: content-hashed ETags, precompressed files, immutable cache headersapp.MapStaticAssets();What You Get for Free
Content-based ETags. The ETag is a hash of the file content, not the last-modified timestamp. Deploy a new version with an identical file? No unnecessary download. Change one byte? Cache invalidates automatically.
Precompressed responses. If you have app.css.br (Brotli) or app.css.gz (gzip) alongside app.css, MapStaticAssets serves the compressed variant automatically based on Accept-Encoding. Blazor and the .NET build system generate these for you.
Immutable cache headers. For fingerprinted files (like Blazor’s app.abc123.css), it sets Cache-Control: max-age=31536000, immutable. The browser caches it forever and never asks again. When the content changes, the filename changes, and the old cache entry is simply abandoned.
Blazor Integration
In a Blazor app, your App.razor references static assets, and the build system fingerprints them:
<link rel="stylesheet" href="@Assets["app.css"]" /><script src="@Assets["app.js"]"></script>@Assets["app.css"] resolves to something like app.a1b2c3d4.css at build time. Combined with MapStaticAssets(), the browser gets an immutable, precompressed file with a year-long cache lifetime. Deploy a new version and the filename changes — instant cache bust, zero stale content.
Razor Pages / MVC
Works the same way with tag helpers:
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />With MapStaticAssets, the version append uses the same content hash, and responses include proper cache headers.
Response Headers Comparison
| Header | UseStaticFiles() | MapStaticAssets() |
|---|---|---|
| ETag | File modified time | Content hash |
| Cache-Control | Not set | max-age=31536000, immutable (fingerprinted) |
| Content-Encoding | Manual middleware | Automatic (br/gz) |
| Vary | Not set | Accept-Encoding |
Key Takeaway
Replace app.UseStaticFiles() with app.MapStaticAssets() and get content-hashed ETags, automatic Brotli/gzip serving, and immutable cache headers, all without configuration. Your CSS will never be stale again, and your users download fewer bytes. One line, done.