Caching is Hard (TM). If you take the time to fully understand the HTTP caching system, it will make sense, and it will become clear that problems with stale content are because you are using it wrong, usually because you set an expires time in the future for content that will change. It's really easy to not understand the caching system, or default to using a framework, and that's where the problem usually lies.
A framework will often choose to default to setting some expires time, often because the framework authors don't really get caching either and don't fully understand why that's not really going to work. (It is natural that they end up here. E-Tags is a much safer default, but to make it efficient requires more work from the framework user; why that is is a bit more than I'd like to get into in this message.)
If content is going to change, you should be using E-Tags, not expiration times. This includes content that you may not think is going to change, like Javascript files, but in fact do sometimes. The best solution in this case is actually to keep creating new URLs and set Expires into the far future, so that if the browser needs "something_8ef38b.js" which had its expires set in 2030, it knows it doesn't even have to hit the server, and if you "change" the file, your new web pages will actually reference "something_f32190.js", a new file. The timestamp-on-the-end trick is the same. I wish more frameworks built this idea in better, it's generally useful.
(Drawing parallels with functional programming's idea of "value" is left as an exercise for the reader.)
I didn't pretend it was new. I was wrapping more context around it, so that it was less "follow this magical recipe" and more "here's why you should do this" (and also "here's a bit on why so many people end up doing this wrong thing"), though a full accounting of HTTP caching would be much longer. I also pointed out similar other things in the comments made by other people.
A framework will often choose to default to setting some expires time, often because the framework authors don't really get caching either and don't fully understand why that's not really going to work. (It is natural that they end up here. E-Tags is a much safer default, but to make it efficient requires more work from the framework user; why that is is a bit more than I'd like to get into in this message.)
If content is going to change, you should be using E-Tags, not expiration times. This includes content that you may not think is going to change, like Javascript files, but in fact do sometimes. The best solution in this case is actually to keep creating new URLs and set Expires into the far future, so that if the browser needs "something_8ef38b.js" which had its expires set in 2030, it knows it doesn't even have to hit the server, and if you "change" the file, your new web pages will actually reference "something_f32190.js", a new file. The timestamp-on-the-end trick is the same. I wish more frameworks built this idea in better, it's generally useful.
(Drawing parallels with functional programming's idea of "value" is left as an exercise for the reader.)