2

I have a nodejs10 application that is deployed to Google App Engine. If a user has previously used the app in Chrome, the app does not work when the app is redeployed. The app is created with create-react-app and built with webpack so css and js are renamed on build but the index.html that references those files is not. So when the user access the site after deploy it will get the cached index.html that references js/css-files that does not exist anymore. This in turn causes Uncaught syntax error: unexpected token '<'

The only way I have found to get the app working again is to open inspector, disable cache and reload the page. Needless to say, this is not something you can ask the end user to do.

I have searched for a solution for this and found similar problems but not exactly the same, or if they are the same there is no solution available.

The express code serves index.html with res.sendFile when the request is not made to an existing static file to make React router work properly:

app.get('*', (req,res) =>{
    try {
        res.sendFile(path.join(public+'/index.html'))
    } catch(e) {
        res.status(404).send()
    }
});

The thing is I recently moved the app from a personal google cloud account to my business account, and I have never had this problem before. So either I have changed something to cause this, or App Engine has changed.

The response headers (response code 304) looks like:

Accept-Ranges: bytes
Cache-Control: public, max-age=0
Content-Encoding: gzip
Content-Length: 1200
Content-Type: text/html; charset=UTF-8
Date: Thu, 03 Sep 2020 20:43:51 GMT
ETag: W/"967-49773873e8"
Last-Modified: Tue, 01 Jan 1980 00:00:01 GMT
Server: Google Frontend
Vary: Accept-Encoding
X-Cloud-Trace-Context: 7f98678c2ba7dc90ad204b67bec766df;o=1
X-Powered-By: Express

So as you can see the "Last-modified" date on the file is Jan 1, 1980. And I have verified that the file actually has that last modified date on app engine. But in the local build folder that is deployed, the file has a current date. Don't know if this is what is causing the problem though.

So I'm kind of lost. I guess I could hack the "last modified" header, but that does not feel like like it's a good solution. And I am also concerned the the same behavior could happen on other files that will cause similar problems. Anybody else run into this that came up with a solution?

  • Indeed, if this is a cache caused issue, you won't be able to solve it, as it's not possible to clear a client-side browser cache. Searching for such error, I could find out it might be related to the routing system and `app.yaml` configuration of your application. Considering that, could you please take a look at this similar case [here](https://stackoverflow.com/questions/54820986/deploy-create-react-app-on-google-app-engine) and check if the changes in these two configuartions help you fix your case? – gso_gabriel Sep 04 '20 at 13:25
  • @gso_gabriel While the linked issue gives the same error message it is not the same problem. In my case everything works fine if I clear the browser cache. I understand that it might not be possible to clear the client cache but at least I could prevent the file to be cached for future versions. In my deployed code there is no problem serving css/js files, it's just that the cached index.html tries to access the wrong files since they change name when rebuilding. – Christoffer Hallqvist Sep 06 '20 at 21:07
  • Hi @ChristofferHallqvist I have posted an answer to provide a possible solution for the removal of caching that might help you. In case it helps you, please, consider upvoting / accepting it. :) – gso_gabriel Sep 07 '20 at 07:26

5 Answers5

3

I had exactly the same problem with the weird timestamps.

It turns out to be behaviour by design as all timestamps are made zero on deploy by app-engine. see: https://issuetracker.google.com/issues/168399701

I solved it in express like this:

app.use("/", express.static(root, { etag: false, lastModified: false }));

Or for individual files:

app.get("/*", async (req, res) => {
  return res.sendFile("index.html", { root, lastModified: false, etag: false });
});
Wouter de Winter
  • 701
  • 7
  • 11
1

As mentioned in the original question, the etag on the index.html is: W/"967-49773873e8"

I recognized that etag, one example of an etag on my appengine + express website is W/"bd1-49773873e8"

After that I noticed that all my etags ended in the same hash: 49773873e8 This is supposed to be a hash of the body of the file being sent.

It seems like something is going wrong in either express or app engine, causing the hash to always end with 49773873e8

The first part is derived from the length of the file, so if 2 files (or the old index.html) are of equal length there is a hash collision.

0

As mentioned in the comments, you won't be to clear a client-side browser cache. So, this wouldn't be a viable solution for you. However, considering the fact that everything works fine, but only the caching issue, I have searched and have found a possible alternative.

It seems that this repository here, called nocache, it's a middleware to turn off caching and that can be used within Javascript. You just need to install it by running npm install --save nocache and then, add the below lines to your code.

const nocache = require('nocache')
app.use(nocache())

This is a solution that I found from this case here, where it's a solution to stop caching on client-side.

gso_gabriel
  • 4,199
  • 1
  • 10
  • 22
  • Well, sure this is one way of "solving" it, but a rather brute force way, and way that shouldn't be necessary in the first place. If cache works properly in the first place, there shouldn't be a need for a hack like this. I an looking for an explanation for why this problem occured in the first place, why the last modified date is 1980, and if that is intended. – Christoffer Hallqvist Sep 09 '20 at 19:39
  • Hi @ChristofferHallqvist it seems that you are not the only one, as this other case [here](https://stackoverflow.com/questions/63813692/why-is-the-last-modified-incorrect-on-gcp-app-engine) has the same situation as you and as mentioned by another person, it's indeed a cache issue. It includes a different possible solution as well. In case you think this case is an issue created by App Engine, you can report it [here](https://issuetracker.google.com/issues/new?component=187191&template=0). – gso_gabriel Sep 10 '20 at 05:56
  • Thanks, it certainly does seem like problem is caused by App Engine, so I'm following the reported issue on the case, and hope that it leads to a proper solution, rather than busting the cache when you shouldn't have to. – Christoffer Hallqvist Oct 01 '20 at 14:00
  • I am also experiencing the same problem, and finding no solution. I am trying the things described here. @ChristofferHallqvist , did you solve it? – rafinskipg Nov 25 '20 at 18:53
0

I suspect that you're also calling app.use(express.static()) with the directory containing index.html. Move your template files to a separate directory from your static content.

0

people.

After struggling I found the right solution.

When serving static files from express, normal middleware don't apply. You need to use the custom function setHeaders from express.static.

  • On one side we are serving index.htmnl when accessing the '/' route. This uses normal express headers
  • On the other side, index.html is being requested through the static serving. This DOES NOT use the other headers.

So we need to cover both cases.

Here is the code:

app.use(express.static('build', { etag: false, lastModified: false, setHeaders: (res, path) => {
  // No cache for index html otherwhise there's gonna be problems loading the scripts
  if (path.indexOf('index.html') !== -1) {
    res.set('Cache-Control', 'no-store')
  }
} }));


app.set('etag', false)

app.disable('x-powered-by');

app.use((req, res, next) => {
  // hide x-powered-by for security reasons
  res.set( 'X-Powered-By', 'some server' );
  // This should apply to other routes
  res.set('Cache-Control', 'no-store')
  next()
})

app.get('/', (req,res) =>{
  // I think this is redundant
  res.set('Cache-Control', 'no-store')
  res.sendFile(path.join(__dirname+'/build/index.html'), { etag: false, lastModified: false });
});

app.get('*', (req,res) =>{
  // I think this is redundant
  res.set('Cache-Control', 'no-store')
  res.sendFile(path.join(__dirname+'/build/index.html'), { etag: false, lastModified: false });
});

const port = process.env.PORT || 8080
app.listen(port, () => console.log('App listening on ' + port));

rafinskipg
  • 509
  • 5
  • 10