65

I have a single page application (angular-js) which is served through IIS. How do I prevent caching of HTML files? The solution needs to be achieved by changing content within either index.html or the web.config, as access to IIS through a management console is not possible.

Some options I am currently investigating are:

IIS is version 7.5 with .NET framework 4

0

7 Answers 7

69

Adding the following into web.config solution worked across Chrome, IE, Firefox, and Safari:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <location path="index.html">
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <add name="Cache-Control" value="no-cache" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
  </location>

</configuration>

This will ensure that the that Cache-Control header is set to no-cache when requesting index.html.

Sign up to request clarification or add additional context in comments.

4 Comments

I think this only works when hitting a url that has directly index.html in it.. all of the requests in an SPA have virtual urls and don't map to real location paths. What can be done with that?
@MichailMichailidis For a SPA you probably have a rule to rewrite your urls to '/'? What if you add that path as well?
While this answer is technically accurate, I think it worth pointing out that Cache-Control: no-cache doesn't disable cache but rather forces the client to check the origin server. As such it works concertedly with the etag header or possibly the unreliable last-modifed header, but it does not outright disable cache. From MDN for no-cache: "Caches must check with the origin server for validation before using the cached copy." In other words, the browser may still use a cached copy if a 304 isn't returned by the server.
to address concerns of @MichailMichailidis ensure your catchall rewrite rule is <action type="Rewrite" url="/index.html" /> or see this link for full Web.config
31

For .NET Core, I used the following.

        app.UseStaticFiles(new StaticFileOptions
        {
            OnPrepareResponse = context =>
            {                   
                if (context.File.Name == "index.html" ) {
                    context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
                    context.Context.Response.Headers.Add("Expires", "-1");
                }
            }
        });

Credit to How to disable browser cache in ASP.NET core rc2?

5 Comments

I'm not very familiar with React (just trying to fix our cache problem), but for me, when debugging, with a breakpoint on the above if statement, the breakpoint is not hit for index.html. I tried putting the code in both app.UseStaticFiles and app.UseSpaStaticFiles. The second one is hit for favicon.ico and manifest.json, located in the same folder as index.html. Any ideas?
@Kjell Rilbe, if breakpoint is not being hit for index.html, verify in browser console its being requested from the server and not browser cache. If the browser is caching, then you would want to address scenario of index.html files that previously did not have cache control headers and thus the browser isn't aware of server's change. At the moment I don't recall how to address this(append useless query string to path, clear browser cache once manually might be helpful for debugging). Good luck.
Thanks, I'll check that once more (think I already did, but to make sure). The breakpoints get hit only twice and that's for the files i mentioned, although the prowser actually seems to fetch a number of other resources. The page index.html is probably fetched as default document/root URL without path or file specified, which is why I put a breakpoint on the if, to see what context.File.name actually comes in in that scenario. But... notthing...
@KjellRilbe if you are using UseSpa(spa => then you should use spa.DefaultPageStaticFileOptions, note, it won't fire in dev server when using UseAngularCli but if you comment out dev server then build the client app manually, then run it, it will hit the breakpoint. Oh and you don't need the if statement, DefaultPageStaticFileOptions only fires for index.html.
Combining this answer with the answer from @RafaelNeto, I ended up with "no-store, max-age=0", and no need for the "Expires" header (it's ignored when max-age is set). Worked perfectly.
16

The main premise for SPA is Never cache index.html

Following the MDN recommendations to prevent caching, we must add the Cache-Control HTTP header with the no-store, max-age=0 value to the resource (the index.html file in our case).

Why no-store instead no-cache?

Whith no-store, the resource is not stored anywhere. With no-cache, the resource may be stored, but it should be validated with the server by the store before use it.

Why max-age=0?

Force to clear pre-existing valid cache responses (no-store doesn't).

In IIS we can manage our app cache configuration through the web.config file. Here is a complete web.config file (must be located in the root directory of our application) that includes the cache configuration for the index.html file as well as the routes configuration (I have added the SPA routing and the HTTP redirection to HTTPS as examples):

<configuration>
  <location path="index.html">
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <add name="Cache-Control" value="no-store, max-age=0" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
  </location>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="HTTP to HTTPS" enabled="true" stopProcessing="true">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTPS}" pattern="^OFF$" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" />
        </rule>
        <rule name="SPA Routes" enabled="true" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/index.html" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

1 Comment

This is the right way to go, configuring caching only for index.html. Thumbs up
12

None of these answers worked for me because, as @MichailMichailidis mentioned, "all of the requests in an SPA have virtual urls and don't map to real location paths", so location path never matches index.html.

I found the solution in this blog post, which links to this answer. Both show you how you can use rewrite module's outboundRules to change the cache-control response header based on a condition.

So this is what my web.config looks like after this configuration:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <outboundRules>
        <rule name="RewriteCacheControlForIndexHtmlFile" 
          preCondition="IsIndexHtmlFile">
          <match serverVariable="RESPONSE_Cache_Control" pattern=".*" />
          <action type="Rewrite" value="max-age=0" />
        </rule>
        <preConditions>
          <preCondition name="IsIndexHtmlFile">
            <add input="{REQUEST_FILENAME}" pattern="index.html" />
          </preCondition>
        </preConditions>
      </outboundRules>
...

1 Comment

This might actually be better for SPA because you might not want to disable caching for all the files.
10

When serving your html files, you can append a random query string. This will prevent the browser from using the old versions even if the file is in the browser cache.

/index.html?rnd=timestamp

The other option is to add the no-cache setting at IIS level. This adds Cache-Control: no-cache in the response which tells browsers to not cache the file. It works from IIS 7 onwards.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!-- Note the use of the 'location' tag to specify which 
       folder this applies to-->
  <location path="index.html">
    <system.webServer>
      <staticContent>
        <clientCache cacheControlMode="DisableCache" />
      </staticContent>
    </system.webServer>
  </location>
</configuration>

3 Comments

Regarding adding a timestamp to the URL - yes this would work but I see this as more of a temporary hack rather than a solution. I don't believe any reputable SPA solutions use this method.
Don't forget your ng routes, css and javascript. The way you do this is web.config for index.html. ?v=1 for everything else e.g. main.css?v=1.
Mostly a landing page like index.html is not requested by in-app code but by an end-user whom will just specify the address in the browser and won't append a random timestamp 😂 so "the other option" is the way to go 👍🏼
9
  <meta http-equiv="cache-control" content="no-cache, must-revalidate, post-check=0, pre-check=0">
  <meta http-equiv="expires" content="0">
  <meta http-equiv="pragma" content="no-cache">

Place these meta tags between <head> and </head> in index.html directly. Of course, you can place these tags in any html file you want to ask the browser not to cache. The meta tags are specific to whatever file or files you manually add them to.

4 Comments

Does that header apply only to the index.html file or also to everything included by it? (css, js, images,...)
i think only for index.html
Really, Sagar.. You think? The word 'index.html' isn't even in that one-liner that you didn't explain.
@Zimano these meta tags are placed statically in the <head> section of the html file. So they apply to that file only. You can place these meta tags in any static html file to request the browser not to cache. Use other methods if you want IIS to turn off browser caching on files more algorithmically.
4

For my case none of previous answears worked, but I found another way, by filter by response content type.

Just add below to web.config

...
<system.webServer>
    <rewrite>
        <outboundRules>
            <rule name="RewriteCacheControlForHtmlFile" preCondition="IsHtmlFile">
                <match serverVariable="RESPONSE_Cache_Control" pattern=".*" />
                <action type="Rewrite" value="no-store, max-age=0" />
            </rule>
            <preConditions>
                <preCondition name="IsHtmlFile">
                    <add input="{RESPONSE_CONTENT_TYPE}" pattern="html$" />
                </preCondition>
            </preConditions>
...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.