Create-react-app: Index.html is being cached making the whole app display an old build

Created on 30 Mar 2017  路  51Comments  路  Source: facebook/create-react-app

So I have a page in production and some of my clients are complaining that they can't see the new changes unless they completely wipe their cache.

I understand that the random hash on the js and css files are to prevent caching issues, but what about the index file itself? What can I do to prevent this file from being cached?

Thank you

question

Most helpful comment

This doesn鈥檛 sound related to me. You鈥檙e probably hitting the service worker. Please read this section about service worker being enabled by default. If you鈥檇 like to disable it, you can opt out too.

All 51 comments

Unfortunately it's really hard to help you without knowing what kind of environment you're deploying in (e.g. web server, etc).

We would love to try to help but this is a configuration problem external of CRA. However, the jist of the problem is that you need to send explicit rules to not cache the file from your web server.

If you provide more information I can try to be more helpful. 馃榾

Thanks @Timer ! Sorry about not being more explicit before.
The server is Azure and I already have this in place in order to prevent caching:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <staticContent>
            <clientCache cacheControlCustom="public"
                         cacheControlMaxAge="00:00:05"
                         cacheControlMode="UseMaxAge" />
        </staticContent>
        <rewrite>
            <rules>
                <rule name="Rewrite to index.html">
                    <match url="index.html|robots.txt|assets|static" />
                    <action type="None" />
                </rule>
                <rule name="Rewrite Index">
                    <match url=".*" />
                    <conditions>
                        <add input="{REQUEST_FILENAME}" pattern="css|js|jpg|jpeg|png|gif|ico|htm|html" negate="true" />

                    </conditions>
                    <action type="Rewrite" url="/" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>  

But it doesn麓t seem to be working for some users.

I thought about changing the index.html to index.php and add some kind of timestamp anticaching, but I saw another issue where in order to do that I would have to eject. And I was trying to avoid that if possible!

Thanks!

Can you inspect the http headers in Chrome and see if the headers are being sent correctly from the server?
Please paste them here.

You can try this too:

<clientCache cacheControlMode="DisableCache" />

You can also put a separate index.php in your public folder if you'd like, and it will get copied to the build output. Not sure if it helps since you'd have to read index.html from it I guess.

AFAIK adding index.php in public folder with this content should done it.

<?php

require('./index.html');

You need to configure your server to send index.php even when there's index.html though

Using php seems like a pretty hacky solution to this unless the headers are coming back correct, which would imply their browser(s) don't support/respect cache requests.

Thanks all for your comments!

@Timer, here are the response headers:

HTTP/1.1 304 Not Modified
Cache-Control: public,max-age=5
Accept-Ranges: bytes
ETag: "80cea5c563a9d21:0"
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Fri, 31 Mar 2017 14:56:49 GMT

Which makes sense to what I included right? That would be 5 seconds?

@viankakrisna I think I will try your solution since it seems the server is sending the proper headers.

That looks right to me (5 seconds). How are your users trying to refresh? F5 doesn't necessarily refresh, and is a commonly bumped into bug-like behavior. Users need to open a new tab.

See http://stackoverflow.com/questions/11245767/is-chrome-ignoring-control-cache-max-age for more info.

They close the browser, and open it again on the next day.

Now, if it wasn't supposed to be cached, shouldn't the response be 200 instead of 304?

Correct, but only in a new tab. Are you seeing a 304?

mmm this is weird, now chrome is giving me this (on a new tab)

Request URL:http://xxx/
Request Method:GET
Status Code:200 OK (from disk cache)
Remote Address:40.114.13.25:80
Response Headers
Accept-Ranges:bytes
Cache-Control:public,max-age=5
Content-Encoding:gzip
Content-Length:485
Content-Type:text/html
Date:Fri, 31 Mar 2017 15:11:02 GMT
ETag:"80cea5c563a9d21:0"
Last-Modified:Thu, 30 Mar 2017 14:41:53 GMT
Server:Microsoft-IIS/8.0
Vary:Accept-Encoding
X-Powered-By:ASP.NET
Request Headers
Provisional headers are shown
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36

200 OK (from disk cache)???

Odd. Maybe you can go oldschool and add this to your head?

<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />

I'm baffled... I also added that snippet. Nothing seems to work I keep getting 304. Every know and then, I get a 200, but it's not consistent...

My next step is to try to do the PHP hack and see how that goes...

Odd, I tried the site and Chrome keeps caching it for me.
I'd try the PHP hack I suppose, I'm not super versed with IIS and tweaking this.

Sorry I can't be of more help. 馃槥

Don't worry @Timer you helped plenty! Thanks!

I couldn't solve my problem yet, but it doesn't seem to be a create-react-app specific issue, so if a collaborator feels like it, just close it. Unless they have other suggestion!

Thanks

I'll go ahead and close this, did you try the php solution?
If you end up figuring this out (without the php hack) maybe we can add a relevant section to the Deployment docs to help people in the future (this probably isn't a IIS specific issue, but headers in general). 馃槃

Please feel free to open another issue / PR in the future if you find a resolution for this.
As always, feel free to post more on this issue -- we still get notifications for it!

@jlubeck Did you try the index.php workaround? If so, did it work?

I think I finally got a personally satisfactory reason(s) for what is going on here! I also had an issue with things being loaded from the cache intermittently, and with inconsistent behaviour across browsers and navigation methods.

It comes down to how browsers implement back/forward (aka history) navigations vs "real" navigations (which are when you hit enter in the actual address bar). If you use history navigation actions, in my case clicking a link to one of our react pages, this would be served from the chrome disk cache, ignoring the fact that index.html has been updated since. But when I typed it into the address bar and hit enter it loaded freshly from the server.

Full background, and solution to the issue is here:
https://stackoverflow.com/a/16506960
here for the full full answer! https://madhatted.com/2013/6/16/you-do-not-understand-browser-history

This problem probably affects people who use routing (i.e. react router) or if you have a half/half react/non-react website and users need to click on a link to get into the react section.

This doesn鈥檛 sound related to me. You鈥檙e probably hitting the service worker. Please read this section about service worker being enabled by default. If you鈥檇 like to disable it, you can opt out too.

I already had disabled the service workers a while back. That was causing me an issue with serving old content, but not this one. I literally only noticed this yesterday when I clicked a link into our react entrypoint and did not happen to type it in the address bar.

Got it, thanks for clarifying!

the docs say:
"If you would prefer not to enable service workers prior to your initial production deployment, then remove the call to registerServiceWorker() from src/index.js."

but have no that line in index.js

I've delete all the files, and copy again from build to my /var/www/html with nginx
but it's loading and old version i realize because it's load a build js file i' dont know where, i'm doing hard reload in chrome, with the anothers paths say me 'page not found' but if i start with the / initial path keep loading old version from the app,
I've copied build files from a new compilation , deleting all the previous files, but keeping showing me old version,

if I want to force don't use service worker and cache must in my index.js add this ?

import { unregister } from './registerServiceWorker';
unregister() ;

or maybe is nginx .. ?

I realize the problem is with chrome cache, in firefox without precedents data , the page charge the last version ... but i don't see clear how i Must to tell the browser that don't use cache ...

I have the same issue with create-react-app hosted on Azure. Did you find a solution, @jlubeck?

The same here. Nginx + production build never refresh in Chrome, in Firefox after you've purged all caches with ctrl+R, any ideas what to do with that?

@denisviklov I ended up unregistering the service worker.

@denisviklov In Chrome not easy like Firefox you must to open developper windows, and click Right on 'Empty cache and Hard Reload' and look at too in 'Application' (developper) and 'Service Worker' on the left you can delete from there.
the problem is unsolvable when the worker cache a page with redirect , you never get the new version, same deleting all the files on your server, the problem of worker if persist hours and days and not possible to manipulate from your app. It's better to disable it until your app is very stable, no redirects... and you can allow some days customers use an old version, for news app is not advisable.

@danyalaytekin How did you do this? The main problem what I have, it's what I deploy everything automatically in docker env.

@webmobiles just wonder why devs don't want add this option into config

I'm facing a similar issue. Mines working fine in desktop. Only mobile browsers seem to be affected by it. Upon checking the debugger of mobile safari in my iphone 6s, found out that the server was serving old index.html and since the old js file that it was referring to was no more available, the page crashed.

The server is apache 2.4 and i've configured it to not cache html files but didn't work.

<filesMatch "\.(html|htm)$"> FileETag None <ifModule mod_headers.c> Header unset ETag Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate" Header set Pragma "no-cache" Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT" </ifModule> </filesMatch>

Not sure, what's wrong. I ended up creating the old .js file that it was referring to by copying the new .js file with it.

I've just remove some lines in serviceworker.js and it stops.

@ngyangjo is not on apache you must change, but on your index.js

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';
import './index.css';
import './i18n';
// import registerServiceWorker from './registerServiceWorker';
import { unregister } from './registerServiceWorker';
unregister();
ReactDOM.render(
  <App />,
  document.getElementById('root'),
);

Thanks for the help. It started working again without having to make any changes. Not sure, what sorcery did that. If I ever stumble upon it, I'll try unregistering it.

I'm writing this comment to confirm the same problem with registerServiceWorker.(I'm not sure if it's an issue or an expected behaviour)
I developed a very simple to do app with create-react-app and Express.js.(you can visit the running app on heroku from here. The only part made problem was Google Auth. For authentication as you know we should redirect the user to Google and the rest of the scenario.
Everything was working well on Local because I use a proxy to pass the api requests to server. For deployment as always I added a handler:

if (process.env.NODE_ENV === 'production') {
    app.use(express.static('frontend/build'));

    const path = require('path');

    app.get('/*', (req, res) => {

        res.sendFile(path.resolve(__dirname, 'frontend', 'build', 'index.html'));

    });

}

It didn't work! When I tried to use Google Auth on the app I encountered the 404. I surprised when app worked perfectly on Safari but not on Chrome, Firefox etc.
I searched over StackOverflow and Github and tried everything suggested but none of them worked.
After hours and hours of debugging and tracking the logs on server I found the server doesn't serve index.html! I tried my app again on Chrome and Firefox but this time before that I used 'empty cache and hard reload' on chrome. And It worked!. I thought maybe it was just a simple caching by browser and it works after clearing the cache. But it didn't. Actually anytime I tried to get to a route which wasn't a part of react-router like '/auth/google' I had to ' clear cache and hard reload' to make it works.
After millions of years I landed here on this page and as @webmobiles suggested(A big thanks to You) I used unregister() and now finally it's working as I expected.
Still I don't know it's a problem with create-react-app, react-router, Express, Passport Js or Heroku! But in my case using 'unregister()' and removing registerServiceWorker as @gaearon and webmobiles mentioned solved my problem.
Now my index js looks like this:
```const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(reducers,{},composeEnhancers(applyMiddleware(reduxThunk)));

store.dispatch({type:AUTH,auth:isUserAuthorized()});

unregister();

ReactDOM.render(

<Provider store={store}> 

    <App/>

</Provider> , 

 document.getElementById('root'));

```
In case you want to see my code it's available here on my GitHub.

Same problem here, it always keeps caching, index.html always comes from serviceWorker,even after disabling browser cache, unless I use incognito mode.

@bevinhex You can fix it in serviceworker.js

it seems service-worder.js is auto generated on build, however I do have a registerServiceWorker.js, not sure how to exclude index.html from there, for the moment, I decided to disable service worker.

I have same issue in Azure with the service worker disabled :(

I have also experienced the same issue. That root problem IS service worker. In order to resolve this issue u have to remove 'register' and substitute with the following as suggested before.

import { unregister } from './registerServiceWorker';
unregister() ;

The reason being, is once service worker is registered on client's browser they would never fetch the latest copy from server. Simply removing serviceWorker from the codebase straightaway will NOT resolve the problem on Users' whom browsers have ur serviceWorkers registered. Use the code above to de-register itself from the user's browser.

For anyone using Azure and still facing this issue AFTER adding code to "unregister", you may still have your old index.html cached in your browser. To solve this I ran the unregister code manually in the chrome console:

navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});

I'm using Azure web apps, had same experience. However, I overcome the issue with url rewrite outboundRules. This is what I have in my web.config. You can removeapplication/json preCondition if you don't need static json files to be cached

<configuration>
    <system.webServer>
        <rewrite>
             <outboundRules>
                <rule name="noCache" preCondition="noCacheContents">
                    <match serverVariable="RESPONSE_Cache-Control" pattern=".*" />
                    <action type="Rewrite" value="no-cache, no-store, must-revalidate" />
                </rule>
                 <preConditions>
                    <preCondition name="noCacheContents" logicalGrouping="MatchAny">
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^application/json" />
                    </preCondition>
                </preConditions>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

Unregistering service worker doesn't seem to be working for me. I'm just building a react app locally. Is there an agreed-upon solution for this with a simple local set-up?

@Ashleybrandon have you tried first to unregister the service worker from the chrome development console?

So I've been using the .update() in a setInterval function to update my service worker every 10s. But when I try to refresh (on Chrome) after it prompts me to refresh, it still shows the old cache unless I do a force refresh shift r... I got cache-control set to max-age=0 for service-worker.js and index.html in the server. Did anyone come across a solution to this?

I had forgotten to add the production NODE_ENV variable to my build script in my package.json. If the production variable is not set, then the registerServiceWorker.js will not refresh on new content properly

"scripts": {
...
    "build": "cross-env NODE_PATH=src NODE_ENV=production react-scripts build",
}

Works for me:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

I was having this type of issue in an environment where we push updates multiple times a day. I tried many of the server-side corrections above (preventing caching, low TTL etc.) in both Windows and Linux, nothing seemed to work. In my case, the service worker was requesting an old copy of the generated js file. Mine was main..js, with the characters being different every time.

For Example:
If I start with main.9s736sjh.js and retranspile from source I would end up with main.9s63hs7w.js in the build folder, but the cached service worker would still request the original main.9s736sjh.js and get a 404 error.

My Solution
I realize this is hacky but it works so far (at least in Firefox). I put this code just inside in my index.html file:

window.onerror = function(e) {
  if(!sessionStorage.getItem("appRefreshed") && e === "SyntaxError: expected expression, got '<'") {
    sessionStorage.setItem("appRefreshed");
    window.location.reload(true);
  }
}

This will add a error handler globally, checking for the open '<' from our 404 page, then check if the site has been refreshed before, via local storage, and if not: perform a force-refresh.

I'm curious if this is an acceptable solution to anyone else!

@mikefreudiger Your very close to the solution, in terms of looking into the index.html. The issue is definitely related to the index.html. The compiled index.html in the 'build' folder, references the newly compiled main..js. What's actually happening, is that the browser itself is caching the index.html. What you need to do is simply make a change on the Server-side so that it always fetches for a fresh copy of index.html.

Here's my nginx.conf entry

    location ~ \.html$ {
        add_header Cache-Control "private, no-cache, no-store, must-revalidate";
        add_header Expires "Sat, 01 Jan 2000 00:00:00 GMT";
        add_header Pragma no-cache;
    }

Hope this helps.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

onelson picture onelson  路  3Comments

xgqfrms-GitHub picture xgqfrms-GitHub  路  3Comments

fson picture fson  路  3Comments

wereHamster picture wereHamster  路  3Comments

dualcnhq picture dualcnhq  路  3Comments