Error Handling Patterns
Comprehensive error handling patterns for building resilient Aurelia applications with graceful degradation and user-friendly feedback.
These patterns show you how to handle errors gracefully across different layers of your Aurelia application - from API calls and routing to form validation and global error boundaries.
1. Global error boundary with user feedback
Goal: Catch unhandled errors anywhere in your app, log them for debugging, and show users a friendly message instead of a blank screen.
Steps
Create an error boundary service that tracks errors and provides recovery options:
import { ILogger, resolve } from '@aurelia/kernel'; import { observable } from '@aurelia/runtime'; export interface AppError { message: string; stack?: string; timestamp: number; context?: string; } export class ErrorBoundaryService { @observable currentError: AppError | null = null; private errors: AppError[] = []; private logger = resolve(ILogger); captureError(error: Error, context?: string) { const appError: AppError = { message: error.message, stack: error.stack, timestamp: Date.now(), context }; this.errors.push(appError); this.currentError = appError; this.logger.error(`[${context || 'Unknown'}] ${error.message}`, error); return appError; } clearError() { this.currentError = null; } getRecentErrors(count: number = 10) { return this.errors.slice(-count); } }Register a global error handler during app startup:
import { Aurelia } from 'aurelia'; import { ErrorBoundaryService } from './error-boundary-service'; export async function main() { const au = Aurelia.app(MyApp); const errorBoundary = au.container.get(ErrorBoundaryService); window.addEventListener('error', (event) => { errorBoundary.captureError(event.error, 'Window'); event.preventDefault(); }); window.addEventListener('unhandledrejection', (event) => { errorBoundary.captureError( new Error(event.reason?.message || 'Unhandled promise rejection'), 'Promise' ); event.preventDefault(); }); await au.start(); }Display errors in your root component with recovery actions:
import { resolve } from '@aurelia/kernel'; import { ErrorBoundaryService } from './error-boundary-service'; export class MyApp { private errorBoundary = resolve(ErrorBoundaryService); reload() { window.location.reload(); } dismissError() { this.errorBoundary.clearError(); } }<div class="error-banner" if.bind="errorBoundary.currentError"> <div class="error-content"> <h3>Something went wrong</h3> <p>${errorBoundary.currentError.message}</p> <div class="error-actions"> <button click.trigger="dismissError()">Dismiss</button> <button click.trigger="reload()">Reload Page</button> </div> </div> </div> <au-viewport></au-viewport>
Checklist
Unhandled errors display in the banner instead of crashing the app
Console logs include full error details for debugging
Users can dismiss transient errors or reload for critical failures
Error history is available for support tickets
2. API error handling with retry and fallback
Goal: Handle API failures gracefully with automatic retries, fallback data, and clear user feedback about network issues.
Steps
Create an API service wrapper with error handling:
Use the service in components with loading and error states:
Display appropriate UI for each state:
Checklist
Failed API calls retry automatically with exponential backoff
Components show loading state during fetch operations
Users see clear error messages when data fails to load
Fallback data prevents empty states from crashing the UI
Users can manually retry failed operations
3. Router navigation error handling
Goal: Handle route navigation failures, missing routes, and guard rejections with appropriate redirects and user feedback.
Steps
Create a navigation error handler:
Configure router with error handling:
Create an error page with helpful actions:
Checklist
Failed navigation redirects to error page instead of blank screen
Authentication failures redirect to login with return URL
Users can retry failed navigation or return home
404 routes show custom not-found page
Navigation errors are logged for debugging
4. Form validation errors with field-level feedback
Goal: Display validation errors inline next to form fields while preventing submission of invalid data.
Steps
Create a form with validation rules and error display:
Display validation errors inline with accessible markup:
Checklist
Validation errors appear inline next to each field
Users see errors on blur and as they type to fix them
Form submission is prevented when validation fails
Server-side errors merge with client-side validation
Submit button is disabled during submission
Error messages are accessible with ARIA attributes
5. Async operation error boundaries with timeout
Goal: Wrap async operations with timeouts and error handling to prevent indefinite loading states.
Steps
Create a timeout utility with error handling:
Use the timeout wrapper in components:
Checklist
Long-running operations timeout with clear error messages
Timeout errors are distinguishable from other failures
Users aren't stuck in infinite loading states
Errors are logged for monitoring
6. Optimistic updates with rollback on error
Goal: Update UI immediately for better UX, but revert changes if the server rejects the update.
Steps
Implement optimistic updates with error recovery:
Provide visual feedback during updates:
Checklist
UI updates immediately when users interact
Failed updates revert to previous state
Users see error notifications for failed operations
Original state is preserved for rollback
Error handling cheat sheet
Global unhandled errors
Error boundary service + window event listeners
ErrorBoundaryService, window.addEventListener
API failures
Retry with exponential backoff
fetchWithErrorHandling, retry counter, delay
Navigation errors
Route guards + error pages
canLoad, loading, error routes
Form validation
Inline field errors + submit prevention
ValidationController, validation-errors
Async timeouts
Promise.race with timeout
withTimeout, TimeoutError
Optimistic updates
State snapshot + rollback
Save original state, try/catch with restore
Best practices
Always show user feedback: Never fail silently - users should know when something goes wrong
Provide recovery actions: Include "Retry", "Go Home", or "Dismiss" buttons
Log errors for debugging: Use
ErrorBoundaryServiceor similar to track errorsUse appropriate error granularity: Global errors for critical failures, inline errors for forms
Test error paths: Don't just test happy paths - simulate failures and verify recovery
Avoid error cascades: Handle errors at the appropriate level to prevent bubbling
Use loading states: Show when async operations are in progress
Set reasonable timeouts: Don't let users wait indefinitely
Make errors actionable: Tell users what they can do to fix the problem
See also
Validation outcome recipes - Field validation errors
Fetch client outcome recipes - API error handling
State outcome recipes - State mutation error recovery
Router hooks - Navigation error handling
Last updated
Was this helpful?