Aspnetcore: React template index.html caching?

Created on 15 May 2018  路  12Comments  路  Source: dotnet/aspnetcore

Hi,

I seem to be facing a problem when deploying a new build to my server.

It seems that index.html is cached by the browser and tries to load the old bundle, which leads to a 404.

Any ideas how to fix this?

Thanks!

area-mvc question

Most helpful comment

This setup works for me (Create React App hosted on Azure App Service).

``c# app.UseSpaStaticFiles(new StaticFileOptions() { OnPrepareResponse = ctx => { if (ctx.Context.Request.Path.StartsWithSegments("/static")) { // Cache all static resources for 1 year (versioned filenames) var headers = ctx.Context.Response.GetTypedHeaders(); headers.CacheControl = new CacheControlHeaderValue { Public = true, MaxAge = TimeSpan.FromDays(365) }; } else { // Do not cache explicit/index.htmlor any other files. See also:DefaultPageStaticFileOptions` below for implicit "/index.html"
var headers = ctx.Context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromDays(0)
};
}
}
});


```c#
app.UseSpa(spa =>
{
    spa.Options.SourcePath = "ClientApp";
    spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions()
    {
        OnPrepareResponse = ctx => {
            // Do not cache implicit `/index.html`.  See also: `UseSpaStaticFiles` above
            var headers = ctx.Context.Response.GetTypedHeaders();
            headers.CacheControl = new CacheControlHeaderValue
            {
                Public = true,
                MaxAge = TimeSpan.FromDays(0)
            };
        }
    };

    if (env.IsDevelopment())
    {
        //spa.UseReactDevelopmentServer(npmScript: "start");
        spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
    }
});
  • Does not cache index or client-side routing (that returns index)

    • Cache-Control: public, max-age=0

  • Caches all /static/* resources for 1 year (versioned/sha'd filenames like javascript bundles)

    • Cache-Control: public, max-age=31536000

  • Does not cache any other resources (manifest.json, service-worker.js, etc)

    • Cache-Control: public, max-age=0

The main issue I had before making this change was because there was not an explicit Cache-Control header but there was a Last-Modified header, browsers would use this to cache the response.

Finally, if neither header is present, then we look for a "Last-Modified" header. If this header is present, then the cache's freshness lifetime is equal to the value of the "Date" header minus the value of the "Last-modified" header divided by 10. This is the simplified heuristic algorithm suggested in RFC 2616 section 13.2.4.

All 12 comments

Hi. It looks like this is a question about how to use ASP.NET Core. While we do our best to look through all the issues filed here, to get a faster response we suggest posting your questions to StackOverflow using the asp.net-core-mvc tag.

@georgiosd, you can configure the Cache-Control headers for static files as documented here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-2.0&tabs=aspnetcore2x

@mkArtakMSFT the SPA templates use UseSpaStaticFiles which also has an overload to set options but from what I could see it doesn't have an overload the allows setting BOTH the SourcePath and the cache options.

@mkArtakMSFT I could have been more specific :)

As long as the StaticFileOptions you've passed don't specify a FileProvider, then it will automatically pick up the RootPath from your DI config: https://github.com/aspnet/JavaScriptServices/blob/15d2f5a898da584433e38c82d5b09c375d9f87b7/src/Microsoft.AspNetCore.SpaServices.Extensions/StaticFiles/SpaStaticFilesExtensions.cs#L95

If you choose to pass a custom FileProvider (which you don't need to if your only goal is to change the caching response headers) then it's up to you to configure it to load files from the desired source location.

I'm not totally sure what you mean by SourcePath - I'm guessing you meant RootPath, but apologies if I am misunderstanding.

Actually I think the confusion is on my end.

I was referring to:

app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });

But that seems to be a different call. The relevant one is:

app.UseStaticFiles();
app.UseSpaStaticFiles(/* static file options */);

@SteveSandersonMS is the call to UseStaticFiles necessary or does UseSpaStaticFiles suffice?

Looks like this issue is troubling more people:

https://github.com/facebook/create-react-app/issues/1910
https://stackoverflow.com/questions/49604821/cache-busting-with-create-react-app

Any many more...

This seems to be because of the service worker from CRA so the cache settings I added don't seem to have much of an effect.

If you have any ideas let me know :)

This setup works for me (Create React App hosted on Azure App Service).

``c# app.UseSpaStaticFiles(new StaticFileOptions() { OnPrepareResponse = ctx => { if (ctx.Context.Request.Path.StartsWithSegments("/static")) { // Cache all static resources for 1 year (versioned filenames) var headers = ctx.Context.Response.GetTypedHeaders(); headers.CacheControl = new CacheControlHeaderValue { Public = true, MaxAge = TimeSpan.FromDays(365) }; } else { // Do not cache explicit/index.htmlor any other files. See also:DefaultPageStaticFileOptions` below for implicit "/index.html"
var headers = ctx.Context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromDays(0)
};
}
}
});


```c#
app.UseSpa(spa =>
{
    spa.Options.SourcePath = "ClientApp";
    spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions()
    {
        OnPrepareResponse = ctx => {
            // Do not cache implicit `/index.html`.  See also: `UseSpaStaticFiles` above
            var headers = ctx.Context.Response.GetTypedHeaders();
            headers.CacheControl = new CacheControlHeaderValue
            {
                Public = true,
                MaxAge = TimeSpan.FromDays(0)
            };
        }
    };

    if (env.IsDevelopment())
    {
        //spa.UseReactDevelopmentServer(npmScript: "start");
        spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
    }
});
  • Does not cache index or client-side routing (that returns index)

    • Cache-Control: public, max-age=0

  • Caches all /static/* resources for 1 year (versioned/sha'd filenames like javascript bundles)

    • Cache-Control: public, max-age=31536000

  • Does not cache any other resources (manifest.json, service-worker.js, etc)

    • Cache-Control: public, max-age=0

The main issue I had before making this change was because there was not an explicit Cache-Control header but there was a Last-Modified header, browsers would use this to cache the response.

Finally, if neither header is present, then we look for a "Last-Modified" header. If this header is present, then the cache's freshness lifetime is equal to the value of the "Date" header minus the value of the "Last-modified" header divided by 10. This is the simplified heuristic algorithm suggested in RFC 2616 section 13.2.4.

I can confirm that @techniq 's solution also works for me. Thanks!

Thanks for contacting us. We believe that the question you've raised have been answered. If you still feel a need to continue the discussion, feel free to reopen it and add your comments.

I tried the solution from @techniq but it is now working, and breakpoints in OnPrepareResponse are never hit.
I copied the code exactly, I also tried without the checks so everything would be given a cache of 365 days, but everything is returned with cache-control | public, max-age=0 so for me, at least, the question has not been answered.
@mkArtakMSFT what is your solution?

Was this page helpful?
0 / 5 - 0 ratings