-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Static Web Assets cache busting mechanism #31922
Comments
@stefanloerwald thanks for contacting us. We don't currently have a solution for this, however you can achieve this today with a middleware that checks for requests to files with the right extensions and adds the appropriate caching headers. |
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process. |
@stefanloerwald can you please also share how you're hosting your site? Is it Blazor WebAssembly of Server project? |
Certainly! I'm actually maintaining a few sites with different hosting models. The one that this is causing an inconvenience at the moment is a Blazor server project behind an nginx reverse proxy. The caching headers are set by nginx for the static assets, so I need to work client-side to make sure I fetch the new version. The solution I proposed doesn't seem too complicated to implement, and I would even try to do it myself, if there is consensus that it's a good idea without negative impact for users. What does the blazor team think about it? |
Just FYI, I'm also running into this issue. This is for production in a Blazor WebAssembly hosted (multiple apps) solution, with scoped CSS coming from private feed NuGet Razor Class libraries. |
This is a must have feature. I can't understand why this wasn't implemented by default from the start. |
Do you have any recommendations and/or examples of these middleware providers in use? Thanks for keeping this on the backlog, but looking to solve this as painlessly as possible in the meantime! |
I am currently iterating fast on an application. Deploying daily, with feedback from our users. Forcing users to do hard refresh is a strong inconvenience for us at the moment. We are on .NET 6 and using the server hosted WASM model. Same as Taylor, pointers to the middleware approach would be highly appreciated and a built in option seems like a must in the long term. |
I'm also on .NET 6 using the server-hosted WASM model, and I'd be interested in the middleware approach as well. Currently need to do a hard refresh for every change in my CSS. |
Count me in as well. Bonus: Edge on iOS does not even clear the cache even when supposedly forced to do so through settings so my Edge on iOS currently serves up an inoperable page (Safari at least properly clears the cache). |
I am currently using this hackaround :
On local dev machine this causes flicker/layout jank on each navigation 😿. But at least our users have the latest stuff, don't need any hard refresh and flicker/layout jank for them is only once per version. |
@petterhoel I use a similar approach (I have hosted apps so server knows version) but the issue is if you have Razor Class Libraries the AppName.styles.css imports the RCL libs CSS without cache busting urls. |
Yes there are many variations of this issue. Hopefully we will see some cache lifetime strategies for assets in the .net 7 time frame from the framework. Hashed file names, based on not the source code content (hash only changes if source changes), would be awesome. |
@mkArtakMSFT Is this still planned for .NET 7? |
Unfortunately, this won't make the cut for .NET 7.0 |
Definitely would be good to see a holistic approach to this issue, as it creeps up in all sort of places and would be easy to prevent well from the framework directly. it's hard to patchwork it as an enduser nicely. (atm having clients that have the wrong css loaded on mobile, where a hard refresh is kinda impossible to do easily) |
Since this unfortunately got cut, yet again, I developed this simple workaround for scoped css from various ideas to solve this issue. It's not optimal, but not too far from it for my use case at least. It's so simple and unintrusive and I can't for the life of me understand why something similar isn't implemented by default.
|
setup wasm as autostart false
and manually add integrity to query string to ensure that most (any modern?) of the browser load new resource if query string change. This snippet is from asp hosted app
Also, with this you actually can force for example cloduflare to cache .wasm / .dll files as there are by default not cache by CDN and always requested from origin which add latency for that use-case. |
Thanks for contacting us. We're moving this issue to the |
We're going to address this as part of #52824 |
I've also run into this issue, but the problem is deeper than just simple CSS files... Javascript files are also not getting cache-busted and is becoming very troublesome to try and figure out a workaround. I've upgraded to .NET 8 from Blazor Server App to Blazor Web App... full component based, which is nice and allows some magic to happen that helps cache busting. in my use case, something as simple as site.css was really problematic in .NET6/7, and was easy to fix:
with .NET 8, moving all _Host.cstml to App.razor, including partial class App.razor.cs, I can now utilize a variable for this ie:
However, I utilize some Javascript class libraries, and leverage on demand loading in OnAfterRenderAsync() of a component:
Unfortunately, this does not work in OnAfterRender, as the propagation of the cascaded value happens after firstRender. Hooking it up in OnParameterSet can work to bust caching on that Javascript file, but then i run into other problems where that Javascript has imports.... ie:
I can't get the cache busted versions embedded into MyServer.js.. i have no clue how to do that. They all need to be busted. So, I end up with something that looks like this being loaded to the browser scripts and css files:
I cannot figure out a workaround, and I think this is a much more systematic problem. I can't ask hundreds of users to CTRL-F5 their browsers muliple times a week. |
@razblack After running into this issue myself again this week, I've taken the following approach which is described here (though without examples): The Cache-Control header is used to tell the browser not to cache anything from now on, but this doesn't help if you're updating an app that the browser may already have cached files for. The Clear-Site-Data header solves this and can be used to tell browsers to clear any existing cached files (and cookies etc if you like), but you only want this to happen once during your application loading, otherwise, the browser will clear the cache for each request/response. Technically you don't need the Using these headers, you also don't need to add versioning to css/js files linked in index.html, assuming they are served from the same domain as your app. The code below is an extension method that adds middleware to add these headers for each request to the server.
Then in
|
How on earth did i miss this?!? lol Thank you @philip-reed , i will be working this 1st thing Monday morning. |
wanted to add, still testing, but header is updated and hotreload is definitely grabbing files and appears to be busting the cache. I simplified the middleware a little as i didn't need environments...
then added this before any redirection, routing, or auth:
|
I guess this could be combined somehow nicely with the actual deploy-version stored in the cookie or localstorage, and send the Clear-Site-Data only after deployment changed. |
this seems to do the trick to fully reload after a fresh deployment. reads build-hash from executable, based on https://stackoverflow.com/a/3634544 (it's not a timestamp nowadays, it's a hash) stores application hash in a cookie, used in Program.cs as app.UseCompiledHashCacheBuster("app-hash-cookiename"). public static class CacheBusterExtension
{
private static int? compiledHash;
public static int CompiledHash => compiledHash ??= RetrieveLinkerHash();
public static string CompiledHashString => CompiledHash.ToString("X");
// http://www.codinghorror.com/blog/2005/04/determining-build-date-the-hard-way.html
private static int RetrieveLinkerHash()
{
const int peHeaderOffset = 60;
const int linkerCompileHashOffset = 8;
var b = new byte[2048];
FileStream s = null;
try
{
s = new FileStream(System.Reflection.Assembly.GetExecutingAssembly().Location, FileMode.Open, FileAccess.Read);
s.Read(b, 0, 2048);
}
finally
{
s?.Close();
}
return BitConverter.ToInt32(b, BitConverter.ToInt32(b, peHeaderOffset) + linkerCompileHashOffset);
}
public static IApplicationBuilder UseCompiledHashCacheBuster(this IApplicationBuilder app, string cookieName) => app.Use(async (context, next) =>
{
if (context.Request.Cookies.ContainsKey(cookieName) == true)
{
var hash = context.Request.Cookies[cookieName];
if (hash != CompiledHashString)
{
context.Response.Headers["Clear-Site-Data"] = "\"cache\"";
}
}
context.Response.Cookies.Append(cookieName, CompiledHashString, new()
{
Secure = true,
Expires = DateTimeOffset.MaxValue
});
await next();
});
} |
Is your feature request related to a problem? Please describe.
Browser cache files (partially depending on the server settings), which can lead to the situation that a scoped CSS file changed, but this is not picked up, because the file name didn't change. Replacing
ProjectName.styles.css
byProjectName.styles.css?hash=123
doesn't produce the usual cache-busting mechanism, because the contents of that file (e.g.@import _content/library/library.bundle.scp.css
) didn't change, only the file referenced by the import changed in content.Describe the solution you'd like
Would it be possible to automatically calculate a file hash of the scoped CSS parts and import them with
@import '_content/library/library.bundle.scp.css?hash=123';
?The text was updated successfully, but these errors were encountered: