I am using angular 12, and I use Service Worker to deploy new versions.
It looks like each time I deploy a new version to production (and for some reason not on staging). Some user receive a bunch of error like
Uncaught (in promise): ChunkLoadError: Loading chunk XXX failed.
There is quite some post about this issue, but it appear to me the solution is case by case.
The difference I have in production compare to stg is just.
I call
enableProdMode();
I have the service worker setup like this :
ServiceWorkerModule.register('ngsw-worker.js', {
enabled:
environment.env === Environment.PRODUCTION || environment.env === Environment.STAGING,
registrationStrategy: 'registerWhenStable:30000',
}),
And My Config looks like
{
"$schema": "../../node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": ["/favicon.ico", "/index.html", "/manifest.webmanifest", "/*.css", "/*.js"]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "lazy",
"resources": {
"files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"]
}
}
]
}
And this is how I show the updates (basically a banner that can be ignored) :
@Component({
selector: 'app-live-update-notifier',
templateUrl: './live-update-notifierponent.html',
styleUrls: ['./live-update-notifierponent.scss'],
})
export class LiveUpdateNotifierComponent implements OnDestroy {
hasUpdate = false;
isIgnored = false;
private subscription = new SubSink();
constructor(
@Inject(DOCUMENT) private document: Document,
private swUpdate: SwUpdate,
private appRef: ApplicationRef,
private changeDetectorRef: ChangeDetectorRef
) {
const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable === true));
const everySixHours$ = interval(6 * 60 * 60 * 1000);
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
if (swUpdate.isEnabled) {
this.subscription.add(
everySixHoursOnceAppIsStable$.subscribe(() => swUpdate.checkForUpdate()),
this.swUpdate.available.subscribe((evt) => {
if (evt.type === 'UPDATE_AVAILABLE') {
this.hasUpdate = true;
this.changeDetectorRef.detectChanges();
}
})
);
}
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
updateApp() {
this.document.location.reload();
}
skipNotification() {
this.isIgnored = true;
}
}
If you have any clue that could guide to the solution, thank you.
EDIT :
I have a bunch of lazy loaded module all across my app. I thought the reason was that some module where not loaded previous to a new version, and when user navigate to them after deploy they would have this chunk problem. But I tried to do emulate this issue by
- editing module A and B
- deploy
- navigate to module A
- editing module A and B
- deploy
- navigate to old module B from old A, so without updating the version of the APP RES : old B still load normally
so I don't think it s this issue
I am using angular 12, and I use Service Worker to deploy new versions.
It looks like each time I deploy a new version to production (and for some reason not on staging). Some user receive a bunch of error like
Uncaught (in promise): ChunkLoadError: Loading chunk XXX failed.
There is quite some post about this issue, but it appear to me the solution is case by case.
The difference I have in production compare to stg is just.
I call
enableProdMode();
I have the service worker setup like this :
ServiceWorkerModule.register('ngsw-worker.js', {
enabled:
environment.env === Environment.PRODUCTION || environment.env === Environment.STAGING,
registrationStrategy: 'registerWhenStable:30000',
}),
And My Config looks like
{
"$schema": "../../node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": ["/favicon.ico", "/index.html", "/manifest.webmanifest", "/*.css", "/*.js"]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "lazy",
"resources": {
"files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"]
}
}
]
}
And this is how I show the updates (basically a banner that can be ignored) :
@Component({
selector: 'app-live-update-notifier',
templateUrl: './live-update-notifier.component.html',
styleUrls: ['./live-update-notifier.component.scss'],
})
export class LiveUpdateNotifierComponent implements OnDestroy {
hasUpdate = false;
isIgnored = false;
private subscription = new SubSink();
constructor(
@Inject(DOCUMENT) private document: Document,
private swUpdate: SwUpdate,
private appRef: ApplicationRef,
private changeDetectorRef: ChangeDetectorRef
) {
const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable === true));
const everySixHours$ = interval(6 * 60 * 60 * 1000);
const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
if (swUpdate.isEnabled) {
this.subscription.add(
everySixHoursOnceAppIsStable$.subscribe(() => swUpdate.checkForUpdate()),
this.swUpdate.available.subscribe((evt) => {
if (evt.type === 'UPDATE_AVAILABLE') {
this.hasUpdate = true;
this.changeDetectorRef.detectChanges();
}
})
);
}
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
updateApp() {
this.document.location.reload();
}
skipNotification() {
this.isIgnored = true;
}
}
If you have any clue that could guide to the solution, thank you.
EDIT :
I have a bunch of lazy loaded module all across my app. I thought the reason was that some module where not loaded previous to a new version, and when user navigate to them after deploy they would have this chunk problem. But I tried to do emulate this issue by
- editing module A and B
- deploy
- navigate to module A
- editing module A and B
- deploy
- navigate to old module B from old A, so without updating the version of the APP RES : old B still load normally
so I don't think it s this issue
Share Improve this question edited Oct 28, 2021 at 5:13 Crocsx asked Oct 26, 2021 at 4:10 CrocsxCrocsx 7,60915 gold badges86 silver badges174 bronze badges 5 |2 Answers
Reset to default 9 +250This can be caused by different reasons. But there is a workaround that I used months ago thanks to spock123.
I quote him:
We are of course using the Service Worker service
swUpdate
to get notifications when a new version is available. However, it seems sometimes not all lazy loaded chunks are updated when there is a new SW version.
Basically the workaround is to reload the application when you face the error bundle not found
that causes the error ChunkLoadError
.
You have to listen for error globally and if the app faced this error you just reload the page.
Instructions to set global error handler
UPDATE:
I qoute from angular.io:
Handling an unrecoverable state
In some cases, the version of the application used by the service worker to serve a client might be in a broken state that cannot be recovered from without a full page reload.
For example, imagine the following scenario:
- A user opens the application for the first time and the service worker caches the latest version of the application. Assume the application's cached assets include
index.html
,main.<main-hash-1>.js
andlazy-chunk.<lazy-hash-1>.js
. - The user closes the application and does not open it for a while.
- After some time, a new version of the application is deployed to the server. This newer version includes the files
index.html
,main.<main-hash-2>.js
andlazy-chunk.<lazy-hash-2>.js
(note that the hashes are different now, because the content of the files changed). The old version is no longer available on the server. - In the meantime, the user's browser decides to evict
lazy-chunk.<lazy-hash-1>.js
from its cache. Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space. - The user opens the application again. The service worker serves the latest version known to it at this point, namely the old version (
index.html
andmain.<main-hash-1>.js
). - At some later point, the application requests the lazy bundle,
lazy-chunk.<lazy-hash-1>.js
. - The service worker is unable to find the asset in the cache (remember that the browser evicted it). Nor is it able to retrieve it from the server (because the server now only has
lazy-chunk.<lazy-hash-2>.js
from the newer version).
In the preceding scenario, the service worker is not able to serve an asset that would normally be cached. That particular application version is broken and there is no way to fix the state of the client without reloading the page. In such cases, the service worker notifies the client by sending an UnrecoverableStateEvent event. Subscribe to SwUpdate#unrecoverable to be notified and handle these errors.
handle-unrecoverable-state.service.ts:
@Injectable()
export class HandleUnrecoverableStateService {
constructor(updates: SwUpdate) {
updates.unrecoverable.subscribe(event => {
notifyUser(
'An error occurred that we cannot recover from:\n' +
event.reason +
'\n\nPlease reload the page.'
);
});
}
}
Configuration for nginx server:
location ~* \.(?:css|js)$ {
add_header Cache-Control "public, max-age=7200";
}
This will tell the browser to cache the files for 2 hours, hopefully by that time, the browser will have refreshed the page and got the new files.
ctrl+shirt+r
while hitting the refresh button. Make sure to enable the preserve console log. I'm not sure what end user will do that normally. I noticed that not giving the Angular App enough time to reload fully will cause the issue. – Vyache Commented Oct 11, 2024 at 21:44