We struggled with this same issue and found that some people's browsers would not even pull the latest version unless they manually refreshed. We had problems with caching at various layers, including the CDN where we hosted files.
We also struggled with maintaining versions and being able to rapidly redeploy a previous version if something goes wrong.
Our solution (using project based on vue-cli Webpack):
1) We build the distribution to have a version specific folder instead of 'static'. This also helps us track builds and 'undo' a deployment if needed. To change the 'static' directory, change 'assetsSubDirectory' under 'build' in index.js and change 'assetsPublicPath' to your CDN path.
2) We use Webpack Assets Manifest to build a manifest.json file pointing to all the assets. Our manifest includes a hash of all files, as its a high security application.
3) We upload the versioned folder (containing the js and css) to our CDN.
4) (Optional) We host a dynamic index.html file on the backend server. The links to the stylesheet and scripts are filled in by the backend server using a template system pulled from the data on the manifest.json (see #5). This is optional as you could use the force-reload option as in the comment below, which isn't a great experience but does work.
5) To publish a new version, we post the manifest.json to the backend server. We do this via a GraphQL endpoint but you could manually put the json file somewhere. We store this in the database and use it to populate the index.html and also use it to verify files using the file hash (to validate our CDN was not hacked).
Result: immediate updates and an easy ability to track and change your versions. We found that it will immediately pull the new version in almost all user's browsers.
Another bonus: We are building an application that requires high security and hosting the index.html on our (already secured) backend enabled us to more easily pass our security audits.
Edit 2/17/19
We found that corporate networks were doing proxy caching, despite no-cache headers. IE 11 also seems to ignore cache headers. Thus, some users were not getting the most up to date versions.
We have a version.json that is incremented/defined at build time. Version number is included in manifest.json. The build bundle is automatically uploaded to S3. We then pass the manifest.json to the backend (we do this on an entry page in Admin area). We then set the "active" version on that UI. This allows us to easily change/revert versions.
The backend puts the "currentVersion" as a Response Header on all requests. If currentVersion !== version (as defined in version.json), then we ask the user to click to refresh their browser (rather than force it on them).