or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-management.mdapplication-status-lifecycle.mdconfiguration-timeouts.mderror-handling.mdindex.mdnavigation-routing.mdparcels-system.md

error-handling.mddocs/

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

```