Error Boundaries

Gracefully handle server-side exceptions with toasts or modals instead of broken UIs.

The Problem

When an HTMX request fails (e.g. throws a 500 Exception), ASP.NET Core usually renders a full HTML error page.

If the request was targeting a specific element (like hx-target="#panel"), this full HTML page gets inserted inside that element, breaking your layout.

SwapErrorBoundaries intercepts these exceptions and returns a graceful partial (like a Toast) instead.


🔒 Configuration

Enable it in your Program.cs:

builder.Services.AddSwapHtmx(options => 
{
    // Enable HTMX specific error handling
    options.ErrorHandling.Enabled = true;
    
    // Optional: Show exception message in toast (useful for Dev)
    options.ErrorHandling.ShowExceptionDetails = builder.Environment.IsDevelopment();
    
    // Optional: Custom error view (default is "_SwapErrorToast")
    options.ErrorHandling.ErrorViewName = "_MyErrorAlert"; 
});

🎨 Creating the Error Toast

Create a partial view Views/Shared/_SwapErrorToast.cshtml. The model is SwapErrorModel.

Use Out-Of-Band Swaps (hx-swap-oob) to ensure the toast appears in the correct place (e.g., a toast container at the bottom of the body), regardless of what the original hx-target was.

@using Swap.Htmx.Models
@model SwapErrorModel

<!-- 
    "beforeend" inserts this toast at the end of #toast-container
    See your Layout.cshtml for the container definition.
-->
<div id="toast-container" hx-swap-oob="beforeend">
    <div class="toast error fade-in">
        <div class="toast-header">
            <strong>Error</strong>
            <button onclick="this.closest('.toast').remove()">×</button>
        </div>
        <div class="toast-body">
            @Model.Message
            
            @if (Model.Exception != null)
            {
                <details>
                    <summary>Technical Details</summary>
                    <pre>@Model.Exception.GetType().Name</pre>
                </details>
            }
        </div>
    </div>
</div>

How It Works

  1. Interception: SwapErrorMiddleware catches unhandled exceptions.
  2. Detection: If it's an HTMX request, it suppresses the default error page.
  3. Response: It returns 200 OK (so HTMX processes the swap) but sets HX-Reswap: none to prevent the main target from being overwritten.
  4. Display: The OOB content in your partial view handles the display (e.g. appending to a toast list).