I noticed that deploying a Sapper site changes the hashes of all the bundles, so that any navigation, dynamic importing etc. breaks for any currently active users of that site.
The experience for the user is that... basically the site just stops working. He/she clicks a link and nothing happens, except for the title changing to 500 and this being printed at the bottom of the page (where it's highly unlikely to get noticed):
500
Loading chunk 3 failed. (error: https://sapper.app/client/0502fa20d1df95e93ff7/tag_$tag.3.js)
In the console, we get:
Failed to load resource: the server responded with a status of 404 (Not Found)
I get why this is happening, but in my opinion it could be handled a bit more gracefully. _Imagine if your site has 10 000 active users simultaneously?_ I guess one could do rolling deployments so that the old instances are sticky until all the users are dropped off, but that's a tad difficult for most of us.
If it's of any help, GatsbyJS bumped into the same problem here: https://github.com/gatsbyjs/gatsby/issues/4779
Oh man, that's a really good point that I hadn't considered. This is definitely a high priority to fix. Thanks
This problem is not unique to Sapper; I've experienced it when deploying React and Angular apps also. To avoid it, something like this works:
This is more of a caching issue, isn't it? When you deploy any site with versioned assets, one would expect that a 30+ day lifespan is fine. Technically, they are immutable, and should even be cached as such. (Most of my projects have a 1Y Cache-Control for versioned assets.
So, I think the real problem is _not_ leveraging a CDN, or using one but purging it between deployments. ZEIT started doing this "as a feature" which is a big mistake IMO.
Proper Cache-Control headers will allow your files to exist for a lifespan of XYZ without requiring that the files actually exist anymore.
Caching won't fix this. A user might load the index page on day 1, then navigate to a new route (and need new assets they never loaded before, thus never cached) on day 5.
(But yes - it is good to mark all the hash-named files as cache-forever-immutable!)
The file(s) wouldn't exist on the user disk, but they should exist on the CDN.
The sapper-template SW is also downloading all routes' entry & chunk files on Day 1, so those assets should already be there (on user disk) for Day 5
While using CDNs is great, sapper should probably still attempt a full refresh in case dynamic imports fail with 404s (on navigation _only_, or else we could potentially get infinite loops in some error conditions).
Perhaps on each request, sapper sets a cookie with the current site version? Client triggers a reload if the version changes, no one browses an old site.
Please we can update the documentation for export to highlight this issue and advise on best practice for mitigating the issue. I'm happy to work on this. What is the best practice? Ensure that hashed files remain available after each deploy, anything else which needs to be done? What is a good caching strategy?
It's not just a problem with export. I've been having the same problem with the server deployment. Solved it for now (I hope) by forking Sapper and setting cache headers for page routes (https://github.com/sveltejs/sapper/blob/master/templates/src/server/middleware/get_page_handler.ts#L45) to no-cache, no-store, must-revalidate.
See also #415.
I think I've just run into exactly the same issue. I was wondering if the service worker could be a possible cause?
My site is simply run using build, not export - but an interesting factor is that the link which broke was one which pointed to an anchor on the page it linked to: https://example.com/some/page#some-anchor - not sure if this is telling or not.
This is definitely a high priority to fix. Thanks
This was the initial response 1 year ago, and I still think this is a serious problem.
I just wanted to bring this issue to @sw-yx 's attention, as he seems to be the currently active maintainer (?).
not a maintainer. just an interested party trying to get up to speed. i agree this is an important issue but also it seems we can tweak the rollup/webpack bundle ourselves to name chunks? (i havent actually thought this through, just offering userland suggestions). ditto adding headers on routes.
This is due to the old assets not being available in a cached/ currently loaded file, right? In which case, since we use dynamic imports for chunks and such, can't we catch any loading errors on import and force a full reload when they occur?
@pngwn that would be my proposed solution, but I was thinking maybe the sorcerors here have better ideas that don鈥檛 require a full reload. But I鈥檇 gladly accept a reload instead of crashing!
Funnily enough, only this morning I set up the reload solution I mentioned earlier in the thread (adding the cookie with Cloudflare workers instead of Sapper, since EVERY request, even CDN-cached requests, must possess the cookie): https://gist.github.com/njbotkin/9a170999e23fb34d4113634a6aba47b0
It's not for everyone, and certainly doesn't resolve this issue, but this is the nicest way I can think of to arbitrarily trigger SPA reloads.
@arggh The only thing that comes to mind is loading the manifest in, so we could grab up to date information from a fresh manifest if a request failed but then what if we just end up getting a cached manifest anyway (since the path to the manifest would need to be constant for this to work)? What if there are substantial changes that render such a manifest completely redundant?
@pngwn you are right, it's probably not really feasible. HMR in production seems like begging for trouble.
Honestly I haven't given much thought to this, as I'd personally just go with the full reload -solution.
To make it nice, Sapper could provide a way to intercept the reload event, giving a chance to provide a nice UI that explains the situation to the user and lets them manually click "Reload site".
HMR in production
As a sidenote, Meteor's solution for dynamic imports work pretty much like that: each module is fetched, cached forever until it actually changes, and will not be downloaded again. Updating a single line of code and releasing a new version of the app does not invalidate all modules. Only the changed modules will be re-fetched. They call it "exact code splitting".
Any update on this? I think the simplest fix would be to have an optional full reload, as per @arggh and @pngwn's solution. Is this or something better in the pipeline?
I think the simplest fix would be to have an optional full reload, as per @arggh and @pngwn's solution
Just to repeat myself, I'd like to improve that solution by offering a hook for the developer to handle this situation gracefully: offer a dialog to reload the page, for example.
@arggh Definitely a good idea! For now I will implement this in the 500 page itself.
@lukeed Could you expand on how you would handle the Cache-Control headers? Thanks!
I think I have an issue where Firefox Nightly for Android is "hard remembering" my webpack sapper app hosted on Netlify (which claims to have Instant Cache Invalidation). Refreshes still lead to Loading chunk $X failed errors.
Most of the suggestions here are not possible on the mobile version of Firefox, the ones that were possible did not fix the issue: https://stackoverflow.com/questions/41636754/how-to-clear-a-service-worker-cache-in-firefox
I don't know which of 1 or more is causing the issue: FFN, Netlify, Service Workers, Sapper. and it's pretty hard to tell when you don't have the issue outside of the phone.
[Edit] I figured out the cause of my issue. I installed Sentry and discovered that Firefox does not support named capture groups in regexes. Sapper should get an official Sentry plugin.
Hi, I'm trying to find the best approach to deal with this problem.
I'm not a fan of the idea of keeping the old files and deleting them in the future: the app did change and I can't simply wait for the user to hard refresh it in order to get the new version. And I'm not a fan of automatically hard refreshing the user browser while he's using it: what if he's writing something for hours and I destroy all his work?
Plus, correct me if I'm wrong: if I deploy a new version of my app, the user won't experience any problem at all unless he navigates to another page. Right? He can totally continue doing what he's already doing and it'll work properly until he'll navigate to another page.
So, what if I store the current version of my app on my database (I'm using firestore) and if that value changes while someone is using the app, I force a hard refresh BUT only when the user navigate?
So, something like:
$app_v = 7
$: $db_app_v= $db.get("app_version") // realtime
$: currentPage && database's app_version != app_version && hardRefresh(currentPage)
Would this work?
Or we can do that, but when we get this error "Failed to load resource: the server responded with a status of 404 (Not Found)". If this error happens we can fetch a JSON file appending a timestamp to query string. On that JSON we store the new version number and if it's different to the current one we hard refresh.
@wavesforthemasses I solve this with a message on the _error.svelte page. It's very generic, but if it's a 500 error, I tell the user there's a new version of the web app available. A button they can click reloads the page and they're now on the latest code.
@mikenikles in the end I did something similar to your solution using this on that page:
$: process.browser === true && error.message.includes("Failed to fetch dynamically imported module") && !window.location.hash.includes("#forceReload") && window.location.replace(${$path}#forceReload)
It seems to work fine. I'll check your solutions too :) just show a message is not a bad idea.
Using the above suggestion worked for me but added an uncomfortable flash before the reload happened.
I'm experimenting with putting this reload logic directly within the navigate method inside the sapper runtime.
Most helpful comment
Oh man, that's a really good point that I hadn't considered. This is definitely a high priority to fix. Thanks