Advanced

Error Handling

Handling errors, displaying error pages, and creating custom error boundaries in Manic.

Error Handling

TL;DR

Manic provides error handling through custom error pages (ErrorOverlay, NotFound, ServerError) and React error boundaries. Use these to gracefully display and recover from errors.

What It Is

Manic supports multiple error handling strategies:

Error TypeComponentPurpose
404 Not FoundNotFoundUnknown routes
Server ErrorServerErrorAPI/server failures
Error BoundaryErrorBoundaryCatch React errors
Error OverlayErrorOverlayFull-page error UI

Prerequisites


Quick Start

404 Not Found

// app/routes/~404.tsx
import React from 'react';

export default function NotFound() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
    </div>
  );
}

Server Error

// app/routes/~500.tsx
import React from 'react';

export default function ServerError() {
  return (
    <div>
      <h1>500 - Server Error</h1>
      <p>Something went wrong. Please try again later.</p>
    </div>
  );
}

How It Works

Error Resolution Flow

Initializing diagram...

Type Definitions

// Error page props (if supported)
interface NotFoundProps {
  error?: Error;
}

interface ServerErrorProps {
  error?: Error;
  reset?: () => void;
}

// Error boundary props
interface ErrorBoundaryProps {
  children: React.ReactNode;
  fallback?: React.ReactNode;
  onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}

Examples

Example 1: Custom 404 Page with URL

// app/routes/~404.tsx
import React from 'react';
import { Link, useRouter } from 'manicjs';

export default function NotFound() {
  const { path } = useRouter();

  return (
    <div className="not-found">
      <h1>404</h1>
      <p>Page not found: {path}</p>

      <nav>
        <a href="/">Go Home</a>
      </nav>
    </div>
  );
}

Example 2: Custom 500 Page

// app/routes/~500.tsx
import React from 'react';

export default function ServerError({ error }: { error?: Error }) {
  const isDev = Bun.env.NODE_ENV === 'development';

  return (
    <div className="error-500">
      <h1>Something went wrong</h1>
      {isDev && error && (
        <pre>{error.message}</pre>
      )}
    </div>
  );
}

Example 3: Error Boundary Component

// app/routes/~components/ErrorBoundary.tsx
import React, { Component, ErrorInfo, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('Error caught:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div>
          <h1>Something went wrong</h1>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

Example 4: Using Error Boundary

// app/routes/dashboard.tsx
import React from 'react';
import { ErrorBoundary } from './~components/ErrorBoundary';

export default function Dashboard() {
  return (
    <ErrorBoundary
      fallback={<div>Failed to load dashboard</div>}
    >
      <DashboardContent />
    </ErrorBoundary>
  );
}

function DashboardContent() {
  // If this throws, ErrorBoundary catches it
  throw new Error('oops');
}

Example 5: API Error Handling

// app/routes/api/users/[id].tsx
// Server-side error handling
export async function GET({ params }: { params: { id: string } }) {
  const user = await db.users.find(params.id);

  if (!user) {
    return Response.json(
      { error: 'User not found' },
      { status: 404 }
    );
  }

  return Response.json(user);
}

Example 6: Client Error Toast

// Custom error hook
import { useState } from 'react';

function useError() {
  const [error, setError] = useState<Error | null>(null);

  const clearError = () => setError(null);
  const handleError = (err: Error) => setError(err);

  return { error, clearError, handleError };
}

// Usage
function MyComponent() {
  const { error, clearError } = useError();

  return error ? (
    <div onClick={clearError}>{error.message}</div>
  ) : (
    <div>Content</div>
  );
}

Advanced Patterns

Pattern 1: Logging Errors

// Error boundary with logging
class ErrorBoundary extends Component {
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // Send to error tracking service
    fetch('/api/errors', {
      method: 'POST',
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        timestamp: Date.now(),
      }),
    });
  }
}

Pattern 2: Retry Logic

// Retry button
function ErrorWithRetry({ error, onRetry }) {
  return (
    <div>
      <p>Error: {error.message}</p>
      <button onClick={onRetry}>Retry</button>
    </div>
  );
}

Pattern 3: Error State in URL

// Navigate to error page programmatically
import { useRouter } from 'manicjs';

function handleError() {
  const router = useRouter();
  router.navigate('/error?code=500');
}

Common Issues

Issue 1: Error Page Not Showing

Problem: Custom error page not rendering.

Check:

  1. File is named correctly (~404.tsx, ~500.tsx)
  2. File is in app/routes/
  3. No route is matching first

Issue 2: Error Boundary Not Catching

Problem: Errors not caught by boundary.

Solution: Ensure boundary wraps the correct children:

// ✗ BAD: Boundary doesn't wrap children
<ErrorBoundary>
  <div>Outside</div>
</ErrorBoundary>
<ChildWithError />

// ✓ GOOD: Boundary wraps all potentially failing children
<ErrorBoundary>
  <div>Inside</div>
  <ChildWithError />
</ErrorBoundary>

Best Practices

Use descriptive error messages that help users understand what happened.

Don't expose stack traces in production — only show in development.

Use error boundaries — they gracefully catch React render errors.

Log errors — send to tracking service for debugging.


See also:

On this page