The Auth layout error handling system provides centralized error alert display across all dashboard pages, eliminating the need for manual error handling code in individual pages.

Error messages automatically display below the navigation bar when users are redirected due to insufficient permissions or other error conditions. The system supports both predefined error codes and custom error messages with three visual alert types.

🤖 Automatic Display

Errors display automatically—no manual error handling code needed in pages.

🎨 Three Alert Types

Error (red), Warning (orange), and Info (blue) styling for different scenarios.

📝 Predefined Codes

Common error codes have user-friendly messages built-in.

⚙️ Custom Messages

Override with custom messages via props for page-specific errors.

No code required! When users are redirected with error parameters, Auth layout automatically displays the error:

---
import Auth from '#layouts/Auth.astro'
import type { AnyRole } from '#utils/role-types'
// Protect page with role guard
const requiredRoles: AnyRole[] = ['admin']
---
<Auth
pageTitle="Admin Panel"
requiredRoles={requiredRoles}
>
<!-- Content only visible to admins -->
<!-- Unauthorized users redirected with ?error=insufficient_permissions -->
<!-- Error displays automatically on destination page -->
</Auth>

What happens:

  1. User lacks required role → Redirects to /dashboard?error=insufficient_permissions
  2. Dashboard page automatically displays: “You do not have permission to access that page.”
  3. Error appears below navigation bar, above page content

Override automatic errors with custom messages:

---
import Auth from '#layouts/Auth.astro'
const saveError = Astro.url.searchParams.get('save_error')
---
<Auth
pageTitle="Settings"
errorMessage={saveError ? "Failed to save settings. Please try again." : undefined}
errorType="error"
>
<!-- Page content -->
</Auth>

The system includes predefined error messages for common scenarios:

Error CodeMessage
insufficient_permissions”You do not have permission to access that page.”
session_expired”Your session has expired. Please sign in again.”
invalid_request”The request was invalid. Please try again.”
not_found”The requested resource was not found.”

Usage:

// In API endpoint or middleware
const redirectUrl = new URL('/dashboard', Astro.url.origin)
redirectUrl.searchParams.set('error', 'session_expired')
return Astro.redirect(redirectUrl.toString())
// User sees: "Your session has expired. Please sign in again."

Control the visual style with errorType prop:

Use for: Critical errors, access denied, authentication failures

<Auth
pageTitle="Admin Panel"
errorMessage="Access denied. Admin privileges required."
errorType="error"
>
<!-- Content -->
</Auth>

Visual: Red background, red border, dark red text

When multiple error sources are present, the system resolves in this order:

  1. Custom errorMessage prop (highest priority)
  2. Predefined error code mapping (from URL ?error=...)
  3. Raw error parameter value (fallback for unknown codes)
  4. No error (when neither prop nor param present)

Example:

---
// URL: /dashboard?error=insufficient_permissions
// But page also has custom error prop
---
<Auth
pageTitle="Dashboard"
errorMessage="Custom error message"
errorType="warning"
>
<!-- Custom message displays, not URL param message -->
</Auth>
---
import Auth from '#layouts/Auth.astro'
import type { AnyRole } from '#utils/role-types'
const requiredRoles: AnyRole[] = ['team_manager']
---
<Auth
pageTitle="Team Management"
requiredRoles={requiredRoles}
>
<!-- Unauthorized access automatically redirects with error -->
</Auth>
---
import Auth from '#layouts/Auth.astro'
const validationError = Astro.url.searchParams.get('validation_error')
const errorMessage = validationError
? "Please check your form inputs and try again."
: undefined
---
<Auth
pageTitle="Create Event"
errorMessage={errorMessage}
errorType="error"
>
<form method="POST" action="/api/events/create">
<!-- Form fields -->
</form>
</Auth>
---
import Auth from '#layouts/Auth.astro'
const successParam = Astro.url.searchParams.get('success')
const successMessage = successParam === 'saved'
? 'Your changes have been saved successfully.'
: undefined
---
<Auth
pageTitle="Settings"
errorMessage={successMessage}
errorType="info"
>
<!-- Settings content -->
</Auth>
---
import Auth from '#layouts/Auth.astro'
const saveError = Astro.url.searchParams.get('save_error')
const deleteError = Astro.url.searchParams.get('delete_error')
const networkError = Astro.url.searchParams.get('network_error')
const errorMessage = saveError
? "Failed to save. Please check your inputs."
: deleteError
? "Cannot delete. Item is referenced by other records."
: networkError
? "Network error. Please check your connection."
: undefined
---
<Auth
pageTitle="User Management"
errorMessage={errorMessage}
errorType="error"
>
<!-- Content -->
</Auth>

Type: string | undefined Default: undefined Description: Custom error message to display in alert

<!-- Automatic (no custom error) -->
<Auth pageTitle="Dashboard" />
<!-- Custom error -->
<Auth
pageTitle="Settings"
errorMessage="Failed to load settings"
/>
<!-- Conditional error -->
<Auth
pageTitle="Profile"
errorMessage={hasError ? "An error occurred" : undefined}
/>

Type: 'error' | 'warning' | 'info' Default: 'error' Description: Visual style of the alert

<!-- Error (red) -->
<Auth errorMessage="Access denied" errorType="error" />
<!-- Warning (orange) -->
<Auth errorMessage="Feature unavailable" errorType="warning" />
<!-- Info (blue) -->
<Auth errorMessage="Update successful" errorType="info" />

API endpoints can set error parameters for display on redirect:

src/pages/api/users/update.ts
import type { APIRoute } from 'astro'
export const POST: APIRoute = async ({ request, redirect }) => {
try {
const data = await request.json()
// Validation
if (!isValid(data)) {
return redirect('/dashboard/users?error=invalid_request')
}
// Process update...
// Success
return redirect('/dashboard/users?success=user_updated')
} catch (error) {
// Error
return redirect('/dashboard/users?error=database_error')
}
}

To add new predefined error codes, update the ERROR_MESSAGES mapping in src/layouts/Auth.astro:

const ERROR_MESSAGES: Record<string, string> = {
// Existing codes
insufficient_permissions: 'You do not have permission to access that page.',
session_expired: 'Your session has expired. Please sign in again.',
// Add new codes
database_error: 'A database error occurred. Please try again later.',
rate_limited: 'Too many requests. Please wait before trying again.',
maintenance: 'System is under maintenance. Please check back later.',
}

Then use in redirects:

redirectUrl.searchParams.set('error', 'rate_limited')
return Astro.redirect(redirectUrl.toString())

Before (Manual approach):

---
import Auth from '#layouts/Auth.astro'
import CardContainer from '#components/astro/CardContainer.astro'
// Manual error checking
const errorParam = Astro.url.searchParams.get('error')
const errorMessage =
errorParam === 'insufficient_permissions'
? 'You do not have permission to access that page.'
: null
---
<Auth pageTitle="Dashboard">
{/* Manual error display */}
{errorMessage && (
<section>
<CardContainer>
<div class="alert alert-error" role="alert">
<p><strong>Access Denied:</strong> {errorMessage}</p>
</div>
</CardContainer>
</section>
)}
<section>
<!-- Dashboard content -->
</section>
</Auth>

After (Automatic approach):

---
import Auth from '#layouts/Auth.astro'
---
<Auth pageTitle="Dashboard">
<section>
<!-- Dashboard content -->
<!-- Error displays automatically above this section -->
</section>
</Auth>

Benefits:

  • ✅ Removed ~15 lines of boilerplate code
  • ✅ Consistent error display across all pages
  • ✅ No manual error handling required
  • ✅ DRY principle (Don’t Repeat Yourself)
<Auth pageTitle="Dashboard">
<!-- Errors display automatically -->
<section>Dashboard content</section>
</Auth>

❌ Don’t: Manually Handle Predefined Errors

Section titled “❌ Don’t: Manually Handle Predefined Errors”
<!-- DON'T DO THIS -->
---
const errorParam = Astro.url.searchParams.get('error')
const errorMessage = errorParam === 'insufficient_permissions' ? '...' : null
---
<Auth pageTitle="Dashboard">
{errorMessage && <div class="alert">{errorMessage}</div>}
</Auth>
<Auth
errorMessage="Failed to save user profile. Please check required fields and try again."
errorType="error"
>
<!-- DON'T DO THIS -->
<Auth errorMessage="Error" errorType="error">
<!-- Success message -->
<Auth errorMessage="Profile updated successfully" errorType="info" />
<!-- Non-critical issue -->
<Auth errorMessage="Some features temporarily unavailable" errorType="warning" />
<!-- Critical error -->
<Auth errorMessage="Access denied" errorType="error" />
<!-- DON'T DO THIS -->
<Auth errorMessage="Success!" errorType="error" />

The Auth layout provides a secondary-nav named slot that lets pages replace or suppress the default DashboardNavigation section.

When secondary-nav is provided, it completely replaces the default nav section (including the <Nav> wrapper). If the slot is absent, the default DashboardNavigation renders unchanged.

// In Auth.astro
const hasSecondaryNav = Astro.slots.has('secondary-nav')
// Slot replaces default nav when present
hasSecondaryNav ? <slot name="secondary-nav" /> : <Nav><DashboardNavigation /></Nav>

Custom navigation with hidden items:

<Auth pageTitle="Dashboard">
<section slot="secondary-nav">
<DashboardNavigation hideItems={['orders']} />
</section>
<!-- page content -->
</Auth>

Entirely custom navigation:

<Auth pageTitle="Settings">
<nav slot="secondary-nav">
<a href="/dashboard">Back to Dashboard</a>
<a href="/settings/profile">Profile</a>
</nav>
</Auth>

Suppress navigation entirely:

<!-- Empty Fragment suppresses default nav -->
<Auth pageTitle="Focused Task">
<Fragment slot="secondary-nav" />
<!-- page content with no navigation rendered -->
</Auth>

The dashboard index page (src/pages/dashboard/index.astro) uses this slot to show all navigation items except Orders:

<Auth pageTitle="Dashboard">
<section slot="secondary-nav">
<DashboardNavigation hideItems={['orders']} />
</section>
<!-- dashboard content -->
</Auth>

Problem: Error parameter in URL but no alert shown

Solutions:

  1. Verify Auth layout version supports error display (check for errorMessage prop)
  2. Check browser DevTools for redirect chain
  3. Inspect URL for error parameter
  4. Check for CSS hiding the alert
  5. Verify Auth layout wraps page content

Problem: Shows raw error code instead of friendly message

Solution: Add error code to ERROR_MESSAGES mapping in Auth.astro

Problem: URL param displays instead of custom prop

Solution: Ensure custom error is not undefined:

❌ <Auth errorMessage={undefined} /> <!-- Falls back to URL param -->
✅ <Auth errorMessage="Custom error" /> <!-- Overrides URL param -->
  • Implementation: src/layouts/Auth.astro
    • Props: Lines 24-33
    • Error mapping: Lines 46-52
    • Error resolution: Lines 54-58
    • Alert display: Lines 122-143
  • Technical Guide: project-docs/02-guides/auth-layout-error-handling.md
  • Example: Auth layout role guards automatically use this system