0
# Error Handling
1
2
Global error handling system for managing application errors and failures in microfrontend architectures.
3
4
## Capabilities
5
6
### Add Error Handler
7
8
Registers a global error handler that will be called when applications or parcels encounter errors during their lifecycle.
9
10
```javascript { .api }
11
/**
12
* Add a global error handler for application and parcel errors
13
* @param handler - Function to handle errors
14
*/
15
function addErrorHandler(handler: (error: AppError) => void): void;
16
17
interface AppError extends Error {
18
appOrParcelName: string;
19
}
20
```
21
22
**Usage Examples:**
23
24
```javascript
25
import { addErrorHandler } from "single-spa";
26
27
// Basic error logging
28
addErrorHandler((error) => {
29
console.error("Single-SPA Error:", error);
30
console.error("Failed app/parcel:", error.appOrParcelName);
31
console.error("Error message:", error.message);
32
console.error("Stack trace:", error.stack);
33
});
34
35
// Error reporting to external service
36
addErrorHandler((error) => {
37
// Send to error tracking service
38
errorTrackingService.captureException(error, {
39
tags: {
40
component: error.appOrParcelName,
41
framework: "single-spa"
42
},
43
extra: {
44
userAgent: navigator.userAgent,
45
url: window.location.href,
46
timestamp: new Date().toISOString()
47
}
48
});
49
});
50
51
// Recovery strategies
52
addErrorHandler((error) => {
53
const appName = error.appOrParcelName;
54
55
// Log error
56
console.error(`Application ${appName} failed:`, error);
57
58
// Attempt recovery based on error type
59
if (error.message.includes("network")) {
60
// Network error - might be temporary
61
console.log(`Scheduling retry for ${appName} due to network error`);
62
scheduleRetry(appName, 5000);
63
} else if (error.message.includes("timeout")) {
64
// Timeout error - increase timeout and retry
65
console.log(`Increasing timeout for ${appName}`);
66
increaseTimeoutAndRetry(appName);
67
} else {
68
// Other errors - mark app as broken
69
console.log(`Marking ${appName} as broken`);
70
markApplicationAsBroken(appName);
71
}
72
});
73
```
74
75
### Remove Error Handler
76
77
Removes a previously registered error handler.
78
79
```javascript { .api }
80
/**
81
* Remove a previously registered error handler
82
* @param handler - The error handler function to remove
83
*/
84
function removeErrorHandler(handler: (error: AppError) => void): void;
85
```
86
87
**Usage Examples:**
88
89
```javascript
90
import { addErrorHandler, removeErrorHandler } from "single-spa";
91
92
// Store handler reference for later removal
93
const myErrorHandler = (error) => {
94
console.log("Handling error:", error);
95
};
96
97
// Add handler
98
addErrorHandler(myErrorHandler);
99
100
// Later, remove the handler
101
removeErrorHandler(myErrorHandler);
102
103
// Conditional error handling
104
class ConditionalErrorHandler {
105
constructor() {
106
this.handler = this.handleError.bind(this);
107
this.enabled = true;
108
}
109
110
enable() {
111
if (!this.enabled) {
112
addErrorHandler(this.handler);
113
this.enabled = true;
114
}
115
}
116
117
disable() {
118
if (this.enabled) {
119
removeErrorHandler(this.handler);
120
this.enabled = false;
121
}
122
}
123
124
handleError(error) {
125
if (this.enabled) {
126
console.error("Conditional handler:", error);
127
}
128
}
129
}
130
```
131
132
## Advanced Error Handling Patterns
133
134
### Error Classification and Recovery
135
136
```javascript
137
import { addErrorHandler, getAppStatus, triggerAppChange } from "single-spa";
138
139
class ErrorManager {
140
constructor() {
141
this.errorCounts = new Map();
142
this.maxRetries = 3;
143
this.retryDelay = 1000;
144
145
addErrorHandler(this.handleError.bind(this));
146
}
147
148
handleError(error) {
149
const appName = error.appOrParcelName;
150
const errorType = this.classifyError(error);
151
152
console.error(`${errorType} error in ${appName}:`, error);
153
154
switch (errorType) {
155
case "LOAD_ERROR":
156
this.handleLoadError(appName, error);
157
break;
158
case "LIFECYCLE_ERROR":
159
this.handleLifecycleError(appName, error);
160
break;
161
case "TIMEOUT_ERROR":
162
this.handleTimeoutError(appName, error);
163
break;
164
case "NETWORK_ERROR":
165
this.handleNetworkError(appName, error);
166
break;
167
default:
168
this.handleGenericError(appName, error);
169
}
170
}
171
172
classifyError(error) {
173
const message = error.message.toLowerCase();
174
175
if (message.includes("loading") || message.includes("import")) {
176
return "LOAD_ERROR";
177
}
178
if (message.includes("timeout")) {
179
return "TIMEOUT_ERROR";
180
}
181
if (message.includes("network") || message.includes("fetch")) {
182
return "NETWORK_ERROR";
183
}
184
if (message.includes("mount") || message.includes("unmount") || message.includes("bootstrap")) {
185
return "LIFECYCLE_ERROR";
186
}
187
188
return "GENERIC_ERROR";
189
}
190
191
async handleLoadError(appName, error) {
192
const count = this.getErrorCount(appName);
193
194
if (count < this.maxRetries) {
195
console.log(`Retrying load for ${appName} (attempt ${count + 1})`);
196
await this.delay(this.retryDelay * Math.pow(2, count)); // Exponential backoff
197
triggerAppChange();
198
} else {
199
console.error(`Max retries exceeded for ${appName}, marking as broken`);
200
this.notifyUser(`Application ${appName} is currently unavailable`);
201
}
202
}
203
204
async handleNetworkError(appName, error) {
205
console.log(`Network error for ${appName}, will retry when network is available`);
206
207
// Wait for network to be available
208
await this.waitForNetwork();
209
triggerAppChange();
210
}
211
212
handleTimeoutError(appName, error) {
213
console.log(`Timeout error for ${appName}, consider increasing timeout`);
214
this.notifyDevelopers(`Timeout issue detected in ${appName}`);
215
}
216
217
getErrorCount(appName) {
218
const count = this.errorCounts.get(appName) || 0;
219
this.errorCounts.set(appName, count + 1);
220
return count;
221
}
222
223
async delay(ms) {
224
return new Promise(resolve => setTimeout(resolve, ms));
225
}
226
227
async waitForNetwork() {
228
return new Promise(resolve => {
229
const checkNetwork = () => {
230
if (navigator.onLine) {
231
resolve();
232
} else {
233
setTimeout(checkNetwork, 1000);
234
}
235
};
236
checkNetwork();
237
});
238
}
239
240
notifyUser(message) {
241
// Show user-friendly error message
242
console.log("User notification:", message);
243
}
244
245
notifyDevelopers(message) {
246
// Send to development team
247
console.log("Developer notification:", message);
248
}
249
}
250
```
251
252
### Error Boundary for Applications
253
254
```javascript
255
import { addErrorHandler, getAppStatus, unloadApplication } from "single-spa";
256
257
class ApplicationErrorBoundary {
258
constructor() {
259
this.failedApps = new Set();
260
this.recoveryAttempts = new Map();
261
this.maxRecoveryAttempts = 2;
262
263
addErrorHandler(this.boundError.bind(this));
264
}
265
266
boundError(error) {
267
const appName = error.appOrParcelName;
268
269
if (this.failedApps.has(appName)) {
270
// App already failed, don't retry
271
return;
272
}
273
274
console.error(`Error boundary caught error in ${appName}:`, error);
275
276
// Mark app as failed
277
this.failedApps.add(appName);
278
279
// Attempt recovery
280
this.attemptRecovery(appName);
281
}
282
283
async attemptRecovery(appName) {
284
const attempts = this.recoveryAttempts.get(appName) || 0;
285
286
if (attempts >= this.maxRecoveryAttempts) {
287
console.error(`Recovery failed for ${appName} after ${attempts} attempts`);
288
this.handleUnrecoverableError(appName);
289
return;
290
}
291
292
console.log(`Attempting recovery for ${appName} (attempt ${attempts + 1})`);
293
this.recoveryAttempts.set(appName, attempts + 1);
294
295
try {
296
// Unload and reload the application
297
await unloadApplication(appName, { waitForUnmount: true });
298
299
// Wait a bit before triggering reload
300
await new Promise(resolve => setTimeout(resolve, 2000));
301
302
// Trigger app change to reload
303
await triggerAppChange();
304
305
// Check if recovery was successful
306
setTimeout(() => {
307
const status = getAppStatus(appName);
308
if (status === "MOUNTED") {
309
console.log(`Recovery successful for ${appName}`);
310
this.failedApps.delete(appName);
311
this.recoveryAttempts.delete(appName);
312
} else {
313
console.log(`Recovery failed for ${appName}, status: ${status}`);
314
this.attemptRecovery(appName);
315
}
316
}, 3000);
317
318
} catch (recoveryError) {
319
console.error(`Recovery attempt failed for ${appName}:`, recoveryError);
320
this.attemptRecovery(appName);
321
}
322
}
323
324
handleUnrecoverableError(appName) {
325
console.error(`Application ${appName} is unrecoverable`);
326
327
// Remove from failed apps to prevent further attempts
328
this.failedApps.delete(appName);
329
this.recoveryAttempts.delete(appName);
330
331
// Show fallback UI
332
this.showFallbackUI(appName);
333
334
// Report to monitoring
335
this.reportUnrecoverableError(appName);
336
}
337
338
showFallbackUI(appName) {
339
// Show user-friendly fallback
340
console.log(`Showing fallback UI for ${appName}`);
341
}
342
343
reportUnrecoverableError(appName) {
344
// Send to error monitoring service
345
console.log(`Reporting unrecoverable error for ${appName}`);
346
}
347
}
348
```
349
350
### Development vs Production Error Handling
351
352
```javascript
353
import { addErrorHandler } from "single-spa";
354
355
const isDevelopment = process.env.NODE_ENV === "development";
356
357
if (isDevelopment) {
358
// Development error handler - verbose logging
359
addErrorHandler((error) => {
360
console.group(`🚨 Single-SPA Error: ${error.appOrParcelName}`);
361
console.error("Error:", error);
362
console.error("Stack:", error.stack);
363
console.error("App/Parcel:", error.appOrParcelName);
364
console.error("Location:", window.location.href);
365
console.error("User Agent:", navigator.userAgent);
366
console.groupEnd();
367
368
// Show development overlay
369
showDevelopmentErrorOverlay(error);
370
});
371
} else {
372
// Production error handler - minimal logging, error reporting
373
addErrorHandler((error) => {
374
// Log minimal info to console
375
console.error(`App ${error.appOrParcelName} failed:`, error.message);
376
377
// Report to error tracking service
378
if (window.errorTracker) {
379
window.errorTracker.captureException(error, {
380
tags: { component: error.appOrParcelName },
381
level: "error"
382
});
383
}
384
385
// Show user-friendly message
386
showUserErrorMessage(error);
387
});
388
}
389
390
function showDevelopmentErrorOverlay(error) {
391
// Development-only error overlay
392
const overlay = document.createElement("div");
393
overlay.innerHTML = `
394
<div style="position: fixed; top: 0; left: 0; right: 0; background: #ff6b6b; color: white; padding: 20px; z-index: 10000;">
395
<h3>Single-SPA Error: ${error.appOrParcelName}</h3>
396
<p>${error.message}</p>
397
<button onclick="this.parentElement.remove()">Dismiss</button>
398
</div>
399
`;
400
document.body.appendChild(overlay);
401
}
402
403
function showUserErrorMessage(error) {
404
// Production user-friendly message
405
console.log(`Showing user message for ${error.appOrParcelName} error`);
406
}
407
```
408
409
## Error Prevention
410
411
```javascript
412
import { addErrorHandler, getAppStatus } from "single-spa";
413
414
// Proactive error monitoring
415
class ErrorPrevention {
416
constructor() {
417
this.healthChecks = new Map();
418
addErrorHandler(this.trackError.bind(this));
419
420
// Run periodic health checks
421
setInterval(this.runHealthChecks.bind(this), 30000); // Every 30 seconds
422
}
423
424
trackError(error) {
425
const appName = error.appOrParcelName;
426
const healthCheck = this.healthChecks.get(appName) || {
427
errorCount: 0,
428
lastError: null,
429
errorTypes: new Set()
430
};
431
432
healthCheck.errorCount++;
433
healthCheck.lastError = error;
434
healthCheck.errorTypes.add(error.message);
435
436
this.healthChecks.set(appName, healthCheck);
437
}
438
439
runHealthChecks() {
440
const apps = Array.from(this.healthChecks.keys());
441
442
apps.forEach(appName => {
443
const health = this.healthChecks.get(appName);
444
const status = getAppStatus(appName);
445
446
// Check for concerning patterns
447
if (health.errorCount > 5) {
448
console.warn(`${appName} has ${health.errorCount} errors - investigate`);
449
}
450
451
if (status === "SKIP_BECAUSE_BROKEN") {
452
console.error(`${appName} is broken - requires attention`);
453
}
454
});
455
}
456
457
getHealthReport() {
458
const report = {};
459
this.healthChecks.forEach((health, appName) => {
460
report[appName] = {
461
errorCount: health.errorCount,
462
lastError: health.lastError?.message,
463
status: getAppStatus(appName),
464
errorTypes: Array.from(health.errorTypes)
465
};
466
});
467
return report;
468
}
469
}
470
```