The ErrorBoundary component, introduced in .NET 6, provides a convenient way to handle exceptions in Blazor components. Instead of letting the entire page crash when an error occurs, you can wrap components with ErrorBoundary to gracefully handle errors and display a friendly error message.
Why Use ErrorBoundary?
| Without ErrorBoundaryWith ErrorBoundary |
| ❌ Entire page crashes | ✅ Only affected component shows error |
| ❌ Poor user experience | ✅ Graceful error handling |
| ❌ User must refresh page | ✅ Can recover without refresh |
| ❌ No error context | ✅ Access to exception details |
Basic Usage
Wrap any component with ErrorBoundary to catch exceptions:
@page "/my-page"
@rendermode InteractiveServer
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
When MyComponent throws an exception, Blazor displays a default error UI instead of crashing the page.
Custom Error Content
Provide a custom error message using ChildContent and ErrorContent:
<ErrorBoundary>
<ChildContent>
<MyComponent />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger">
<h5>Something went wrong!</h5>
<p>An error occurred. Please try again later.</p>
</div>
</ErrorContent>
</ErrorBoundary>
Accessing Exception Details
Use the Context parameter to access the caught exception:
<ErrorBoundary>
<ChildContent>
<MyComponent />
</ChildContent>
<ErrorContent Context="exception">
<div class="alert alert-danger">
<h5>Error Details</h5>
<p><strong>Type:</strong> @exception.GetType().Name</p>
<p><strong>Message:</strong> @exception.Message</p>
</div>
</ErrorContent>
</ErrorBoundary>
Security Note: In production, avoid showing detailed exception messages to users. Log them server-side instead.
Recovering from Errors
Use the Recover() method to reset the error state and re-render the component:
<ErrorBoundary @ref="errorBoundary">
<ChildContent>
<MyComponent />
</ChildContent>
<ErrorContent Context="ex">
<div class="alert alert-warning">
<p>Error: @ex.Message</p>
<button class="btn btn-primary" @onclick="HandleRecover">
Try Again
</button>
</div>
</ErrorContent>
</ErrorBoundary>
@code {
private ErrorBoundary? errorBoundary;
private void HandleRecover()
{
errorBoundary?.Recover();
}
}
What Recover() Does
- Clears the error state
- Re-renders the
ChildContent - Resets the wrapped component to its initial state
Nested ErrorBoundaries
Wrap individual components to isolate failures:
<div class="row">
<div class="col-md-4">
<ErrorBoundary>
<ChildContent>
<WidgetA />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger">Widget A failed</div>
</ErrorContent>
</ErrorBoundary>
</div>
<div class="col-md-4">
<ErrorBoundary>
<ChildContent>
<WidgetB />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger">Widget B failed</div>
</ErrorContent>
</ErrorBoundary>
</div>
<div class="col-md-4">
<ErrorBoundary>
<ChildContent>
<WidgetC />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger">Widget C failed</div>
</ErrorContent>
</ErrorBoundary>
</div>
</div>
Result: If Widget B throws an exception, only Widget B shows an error. Widget A and Widget C continue working normally.
ErrorBoundary in Layouts
Wrap the entire page content in a layout to catch any unhandled exceptions:
@inherits LayoutComponentBase
<div class="page">
<Sidebar />
<main>
<ErrorBoundary @ref="errorBoundary">
<ChildContent>
@Body
</ChildContent>
<ErrorContent Context="ex">
<div class="container mt-5">
<div class="alert alert-danger">
<h4>An error occurred</h4>
<p>We're sorry, but something went wrong.</p>
<button class="btn btn-primary" @onclick="Recover">
Return to Home
</button>
</div>
</div>
</ErrorContent>
</ErrorBoundary>
</main>
</div>
@code {
private ErrorBoundary? errorBoundary;
private void Recover()
{
errorBoundary?.Recover();
NavigationManager.NavigateTo("/");
}
}
Complete Example
BuggyCounter.razor (Component that throws)
<div class="border rounded p-3">
<h6>@Title</h6>
<p>Current count: @currentCount</p>
<button class="btn btn-danger" @onclick="IncrementCount">
Click me (Throws at 3)
</button>
</div>
@code {
[Parameter]
public string Title { get; set; } = "Buggy Counter";
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
if (currentCount >= 3)
{
throw new InvalidOperationException(
$"Counter reached {currentCount}! This is a simulated error.");
}
}
}
ErrorBoundaryDemo.razor (Page using ErrorBoundary)
@page "/error-boundary-demo"
@rendermode InteractiveServer
<h1>ErrorBoundary Demo</h1>
<ErrorBoundary @ref="errorBoundary">
<ChildContent>
<BuggyCounter />
</ChildContent>
<ErrorContent Context="exception">
<div class="alert alert-danger">
<h5>Oops! Something went wrong</h5>
<p><strong>Error:</strong> @exception.Message</p>
<button class="btn btn-primary" @onclick="HandleRecover">
Try Again
</button>
</div>
</ErrorContent>
</ErrorBoundary>
@code {
private ErrorBoundary? errorBoundary;
private void HandleRecover()
{
errorBoundary?.Recover();
}
}
ErrorBoundary with Logging
Log errors for monitoring while showing a friendly message to users:
@inject ILogger<MyPage> Logger
<ErrorBoundary @ref="errorBoundary">
<ChildContent>
<MyComponent />
</ChildContent>
<ErrorContent Context="ex">
@{
LogError(ex);
}
<div class="alert alert-danger">
<p>An unexpected error occurred. Our team has been notified.</p>
<button class="btn btn-primary" @onclick="Recover">Try Again</button>
</div>
</ErrorContent>
</ErrorBoundary>
@code {
private ErrorBoundary? errorBoundary;
private void LogError(Exception ex)
{
Logger.LogError(ex, "Error in MyComponent: {Message}", ex.Message);
}
private void Recover()
{
errorBoundary?.Recover();
}
}
Default Error UI Styling
When no ErrorContent is provided, Blazor shows a default error UI. Customize it with CSS:
/* In your app.css */
.blazor-error-boundary {
background: #ffcccc;
padding: 1rem;
border: 1px solid #ff0000;
border-radius: 4px;
}
.blazor-error-boundary::after {
content: "An error occurred. Please try again.";
color: #cc0000;
font-weight: bold;
}
Limitations
| LimitationDescription |
| Render-time only | Only catches errors during rendering, not in event handlers by default |
| No async lifecycle | Doesn't catch errors in OnInitializedAsync after first render |
| Client-side only | For Blazor WebAssembly, server errors need separate handling |
Handling Event Handler Errors
For errors in event handlers, wrap the code in try-catch:
@code {
private async Task HandleClick()
{
try
{
await SomeRiskyOperationAsync();
}
catch (Exception ex)
{
// Handle or log the error
errorMessage = ex.Message;
}
}
}
Best Practices
- Wrap critical components - Don't wrap everything; focus on components that may fail
- Provide recovery options - Give users a way to retry or navigate away
- Log errors server-side - Don't expose stack traces to users
- Use nested boundaries - Isolate failures to minimize impact
- Test error scenarios - Verify your error handling works as expected
Summary
| FeatureDescription |
<ErrorBoundary> | Wraps components to catch exceptions |
<ChildContent> | The protected component(s) |
<ErrorContent> | Custom UI shown on error |
Context="ex" | Access the caught exception |
@ref + Recover() | Reset error state and re-render |