Force refresh cached index.html file in Node express - node.js

What is the best way to make sure all users get a fresh index.html instead of cached index.html?
So last week I tried to make the user cache the js and css bundles for a Single page application. I did it adding max age in the server.js file:
app.use(express.static('build', { maxAge: '365d' }));
The problem is that the index.html file is also in the build folder. Which means that it`s being cached client side for 365 days...
So even though I have cache busting on the js and css bundles, the index.html file is always the same and therefore the bundles stays the same because of the old hash.
I have now changed the server to use Etag instead:
app.use(express.static('build', { etag: true }));
This works great.
The problem is that i pushed this to production and now all the users have an stale index.html.
I use Node express and AWS Application Load balancer.
The endpoint which sends the index.html looks like this:
app.get('*', (request, response) => {
response.sendFile(path.join(__dirname, 'build/index.html'));
});
Does anyone have a smart solution?
I have looked into using a php file like this: https://github.com/facebook/create-react-app/issues/1910 . Is that the best solution?

I had a related problem where, after a deployment, users would need to hard refresh in certain browsers. It was due to blindly caching all build files, including the index.html.
Here's how we resolved it:
app
.use(express.static(BUILD, {
maxAge: '30 days',
setHeaders: (res, path) => {
if (express.static.mime.lookup(path) === 'text/html') {
// Skip cache on html to load new builds.
res.setHeader('Cache-Control', 'public, max-age=0');
}
}
}))
.get('/*', (req, res) => res.sendFile(HTML));

You can send header no-cache when using index.html route
For example:
app.get('/', (request, response) => {
response.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
response.header('Expires', '-1');
response.header('Pragma', 'no-cache');
response.sendFile(path.join(__dirname, 'build/index.html'));
});

Related

react and node js serving from single port endpoint not found

Im making a simple app with react and node. On the react side I create a note with a unique-id and then the note can be found at mypage.com/unique-id using react-router-dom and useEffect in the loaded component to get the data from the database. This works perfect as long as my frontend and backend are being served from different ports. When I build the react app and place it in my nodejs app the app will work fine, until I reload mypage.com/unique-id and then im hit with a 404 on my server side. I have a bit of a grasp of what is going on - everything is being served from localhost:3001/ so when i reload localhost:3001/unique-id my server is looking for a route for that, which doesnt exist on my server side. I'm not sure how to connect the dots to serve my front end page at localhost:3001/unique-id with data from the server for that unique id. If i makea route on my server side it would just send back the raw data without serving the page that is suppose ot be at localhost:3001/unique-id
I had faced the same issue and it got resolved by adding * into the route
before route
app.get("/", (req, res) => {
res.header("Cache-Control", "max-age=-1");
res.header("Expires", "-1");
res.header("Pragma", "no-cache");
res.sendFile(path.join(__dirname, "build/index.html"));
});
after
app.get("/*", (req, res) => {
res.header("Cache-Control", "max-age=-1");
res.header("Expires", "-1");
res.header("Pragma", "no-cache");
res.sendFile(path.join(__dirname, "build/index.html"));
});

Serving webpack generated CSS file via CDN in Heroku CI environment

I have an Node.JS Meteor app that I bundle with webpack, that generates a CSS file with a hash in the name: [hash].bundle.css. I can set the publicPath to the CDN domain:
output: {
publicPath: 'https://xxx.cloudfront.com/',
},
On Heroku the bundle will be generated in the staging-environment and the generated slug will then be moved to the live-environment (including the css file).
When there is a change in the CSS, there will be a new hash in the staging-environment. When the site is opened (testing...) cloudfront will be asking the live-environment for that file, but the Node.JS-server responses with the app-HTML, that is issuing a not-found error on the browser.
Idea: Making the CDN fallback to staging
This is recommended in ther heroku documentation. But since the app-server does not respond with a 404 http error, cloudfront will not look at the staging server.
Problem: serving a 404 http error for missing files
This sound's not to difficult. Meteor webapp uses connect and I use FlowRouter on the client side, so I can:
WebApp.connectHandlers.use('/', function(req, res, next) {
if(FlowRouter.matchPath(req.url).route.name == 'not-found') {
res.writeHead(404);
res.end('Not found.');
} else {
return next();
}
});
But: there is many other connectHandlers that I would need to know, and make FileSystem checks. I tried going down that road, but it seems endless, maintenance intensive and not fail-proof.
Idea: Using Meteor's ?meteor_css_resource=1
There is a Meteor specific treatment of a css file with a query parameter xx.css?meteor_css_resource=1, but that won't be counting as a 404 error for the CDN to make the request to staging again.
Instead of checking all available connectHandlers, we filter just the .css files.
WebApp.connectHandlers.use('/', function(req, res, next) {
const urlParts = url.parse(req.url)
if(urlParts.pathname.endsWith('.css')) {
res.writeHead(404)
res.end('CSS file not found.')
} else {
return next()
}
}

NodeJS Express — Serve generated index.html public file without saving it

When using express, the expectation is that you'll serve a public directory.
const app = express();
app.use('/', express.static('./public/'));
Is there a way I could serve a generated file instead? For my application, it would be much more convenient if I could build the index.html directly, then serve that 'file' directly from memory, without having to save it just to then serve it via 'use'.
the expectation is that you'll serve a public directory
I don't think that is the expectation at all. Many applications just use routes instead making a REST micro service.
There are two ways you can do what you want to do.
Use a templating engine with NodeJS and just res.render() the template. Check this out for more information, even though the article is using .pug you can use these ones as well. Popular ones are ejs, handlebars
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!' })
})
Or you can write everything inside res.send() for example:
app.get('/', function (req, res) {
//set the appropriate HTTP header
res.setHeader('Content-Type', 'text/html');
//send multiple responses to the client
res.send('<h1>This is the response</h1>');
});

SailsJs: getting "304 not modified" page after deploy

I'm trying to update my SailsJs application to fix the blank page wich I'm geting on Heroku, but I'm getting the status 304 not modified from browser.
I think I did everything I need to fix the blank page on my app, but I dont know why the page is not updating on Heroku
My Procfile:
web: node app.js
I appreciate if anyone can help.
Thanks!
I don't think that you need to fix anything. If you try to call the same url using POSTMAN or different browser you should get status 200.
You can add this to a policy instead and then just apply it to the specific pages/requests you want - if you do not want to set this for the entire application.
/api/policies/nocache.js:
/**
* Sets no-cache header in response.
*/
module.exports = function (req, res, next) {
sails.log.info("Applying disable cache policy");
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
res.header('Expires', '-1');
res.header('Pragma', 'no-cache');
next();
};
Don't forget to include nocatche into config/policy.js
'*': 'nocatche'

Express - Setting different maxAge for certain files

I'm building a single page application using Express for the backend and AngularJS for the frontend and I'm having some cache problems.
I'm not using express views, only serving files with express.static middleware.
My static files are in public/app or public/dist depending on enviroment (dist has minified files).
app.use app.router
app.use express.static(cwd + '/public/' + publicFolder, maxAge: MAX_AGE)
When requesting '/', app.router verifies if user is logged in, and if everything is ok then index.html is served via express.static (I just call next() in the controller). If user is not logged in, it gets redirected to login.html
My problem is that if I set maxAge, my index.html file gets cached and the first request to '/' doesn't go through router. I can enter the app even if I'm not logged in.
If I set maxAge to 0, the problem goes away, but I want to cache all my *.js and *.css files.
What is the correct approach to this kind of problems? Start using views? Different express.static mount points?
You can always define individual routes without necessarily using views (although view templates aren't a bad idea). In this way you could define index.html just to apply the special case maxAge.
Just be sure to put the route before the static middleware.
If you wanted you could even use send, the same static server that the static middleware uses behind the scenes. Something like:
// install send from npm
var send = require("send");
app.get("/index.html", function (req, res) {
send(req, "/index.html")
.maxage(0)
.root(__dirname + "/public")
.pipe(res);
});
Or the more low level stream way, something like:
app.get("/index.html", function (req, res) {
res.setHeader('Content-Type', 'text/html');
res.setHeader('Cache-Control', 'public, max-age=0');
// Note that you'd probably want to stat the file for `content-length`
// as well. This is just an example.
var stream = fs.createReadStream(__dirname + '/public/index.html');
stream.pipe(res);
});

Resources