Runtime Errors

Troubleshooting runtime errors, component issues, and application crashes during development and production.

Runtime Errors

This section addresses errors that occur while the application is running, including component rendering issues, API route problems, and application crashes. These errors can affect both development and production environments.

Component Rendering Errors

Issue: Hydration mismatch errors

Symptoms:

  • Console warnings about hydration
  • Different server/client rendering
  • Content "jumping" or changing after page load
  • React hydration warnings

Understanding Hydration: Hydration occurs when React takes over the server-rendered HTML and makes it interactive. Mismatches happen when the server-rendered content differs from what React expects on the client.

Common Causes and Solutions:

// ❌ Problem: Using browser-only APIs during SSR
function Component() {
  const [mounted, setMounted] = useState(false)
  
  // This will cause hydration mismatch
  const theme = localStorage.getItem('theme') || 'light'
  
  return <div className={theme}>Content</div>
}

// ✅ Solution: Use useEffect to handle client-only code
function Component() {
  const [mounted, setMounted] = useState(false)
  const [theme, setTheme] = useState('light')
  
  useEffect(() => {
    setMounted(true)
    setTheme(localStorage.getItem('theme') || 'light')
  }, [])
  
  if (!mounted) {
    return <div className="light">Content</div> // Consistent with SSR
  }
  
  return <div className={theme}>Content</div>
}

// ✅ Alternative: Use dynamic imports with ssr: false
const ClientOnlyComponent = dynamic(() => import('./ClientOnlyComponent'), {
  ssr: false,
  loading: () => <div>Loading...</div>
})

function App() {
  return (
    <div>
      <h1>Server-rendered content</h1>
      <ClientOnlyComponent />
    </div>
  )
}

Date/Time Hydration Issues:

// ❌ Problem: Different date formatting on server/client
function TimeDisplay() {
  const now = new Date().toLocaleString()
  return <span>{now}</span>
}

// ✅ Solution: Consistent date handling
function TimeDisplay() {
  const [time, setTime] = useState<string>()
  
  useEffect(() => {
    setTime(new Date().toLocaleString())
  }, [])
  
  // Show placeholder during hydration
  return <span>{time || '...'}</span>
}

// ✅ Better solution: Use suppressHydrationWarning for time-sensitive content
function TimeDisplay() {
  const [time, setTime] = useState(() => new Date().toLocaleString())
  
  useEffect(() => {
    const timer = setInterval(() => {
      setTime(new Date().toLocaleString())
    }, 1000)
    
    return () => clearInterval(timer)
  }, [])
  
  return <span suppressHydrationWarning>{time}</span>
}

Random Data Hydration Issues:

// ❌ Problem: Random data changes between server and client
function RandomQuote() {
  const quotes = ['Quote 1', 'Quote 2', 'Quote 3']
  const randomQuote = quotes[Math.floor(Math.random() * quotes.length)]
  
  return <blockquote>{randomQuote}</blockquote>
}

// ✅ Solution: Deterministic initial state
function RandomQuote() {
  const quotes = ['Quote 1', 'Quote 2', 'Quote 3']
  const [quote, setQuote] = useState(quotes[0]) // Consistent initial state
  
  useEffect(() => {
    // Randomize after hydration
    setQuote(quotes[Math.floor(Math.random() * quotes.length)])
  }, [])
  
  return <blockquote>{quote}</blockquote>
}

Issue: "Cannot read properties of undefined" errors

Symptoms:

  • Runtime errors accessing undefined properties
  • Application crashes with property access errors
  • Intermittent errors when data is loading

Solutions:

// ❌ Problem: Accessing nested properties without checks
function UserProfile({ user }) {
  return (
    <div>
      <h1>{user.profile.name}</h1>
      <p>{user.profile.email}</p>
      <img src={user.profile.avatar.url} alt="Avatar" />
    </div>
  )
}

// ✅ Solution 1: Optional chaining and nullish coalescing
function UserProfile({ user }) {
  return (
    <div>
      <h1>{user?.profile?.name ?? 'Unknown User'}</h1>
      <p>{user?.profile?.email ?? 'No email'}</p>
      <img 
        src={user?.profile?.avatar?.url ?? '/default-avatar.png'} 
        alt="Avatar" 
      />
    </div>
  )
}

// ✅ Solution 2: Default values with destructuring
function UserProfile({ user = {} }) {
  const { 
    profile: {
      name = 'Unknown User',
      email = 'No email',
      avatar: { url: avatarUrl = '/default-avatar.png' } = {}
    } = {}
  } = user
  
  return (
    <div>
      <h1>{name}</h1>
      <p>{email}</p>
      <img src={avatarUrl} alt="Avatar" />
    </div>
  )
}

// ✅ Solution 3: Type guards with early returns
function UserProfile({ user }) {
  if (!user?.profile) {
    return <div>Loading user profile...</div>
  }
  
  const { name, email, avatar } = user.profile
  
  return (
    <div>
      <h1>{name || 'Unknown User'}</h1>
      <p>{email || 'No email'}</p>
      <img 
        src={avatar?.url || '/default-avatar.png'} 
        alt="Avatar" 
      />
    </div>
  )
}

// ✅ Solution 4: Using custom hook for safe data access
function useSafeUser(user) {
  return useMemo(() => {
    if (!user) return null
    
    return {
      name: user.profile?.name || 'Unknown User',
      email: user.profile?.email || 'No email',
      avatarUrl: user.profile?.avatar?.url || '/default-avatar.png',
      isComplete: Boolean(user.profile?.name && user.profile?.email)
    }
  }, [user])
}

function UserProfile({ user }) {
  const safeUser = useSafeUser(user)
  
  if (!safeUser) {
    return <div>Loading...</div>
  }
  
  return (
    <div>
      <h1>{safeUser.name}</h1>
      <p>{safeUser.email}</p>
      <img src={safeUser.avatarUrl} alt="Avatar" />
      {!safeUser.isComplete && <p>Profile incomplete</p>}
    </div>
  )
}

Issue: State update errors

Symptoms:

  • "Cannot update component while rendering" warnings
  • Infinite re-render loops
  • State not updating as expected

Solutions:

// ❌ Problem: Setting state during render
function Counter() {
  const [count, setCount] = useState(0)
  
  // This causes infinite loop
  if (count > 10) {
    setCount(0) // ❌ Don't do this
  }
  
  return <div>{count}</div>
}

// ✅ Solution: Use useEffect for side effects
function Counter() {
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    if (count > 10) {
      setCount(0)
    }
  }, [count])
  
  return <div>{count}</div>
}

// ✅ Better solution: Handle logic in event handlers
function Counter() {
  const [count, setCount] = useState(0)
  
  const increment = () => {
    setCount(prev => prev + 1 > 10 ? 0 : prev + 1)
  }
  
  return (
    <div>
      <div>{count}</div>
      <button onClick={increment}>Increment</button>
    </div>
  )
}

// ❌ Problem: Stale closure in useEffect
function Timer() {
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1) // ❌ Stale closure
    }, 1000)
    
    return () => clearInterval(timer)
  }, []) // Empty dependency array causes stale closure
  
  return <div>{count}</div>
}

// ✅ Solution: Use functional state updates
function Timer() {
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prev => prev + 1) // ✅ Always gets current value
    }, 1000)
    
    return () => clearInterval(timer)
  }, [])
  
  return <div>{count}</div>
}

API Route Errors

Issue: API routes returning 404 errors

Symptoms:

  • API endpoints not found
  • Routing not working correctly
  • Method not allowed errors

File Structure Check:

# ✅ Correct App Router structure
app/
├── api/
│   ├── products/
│   │   ├── route.ts          # /api/products
│   │   └── [id]/
│   │       └── route.ts      # /api/products/[id]
│   ├── auth/
│   │   └── callback/
│   │       └── route.ts      # /api/auth/callback
│   └── webhooks/
│       └── stripe/
│           └── route.ts      # /api/webhooks/stripe

# ❌ Incorrect structure (Pages Router style)
pages/
├── api/
│   ├── products.ts           # Don't use in App Router
│   └── auth/
│       └── callback.ts       # Don't use in App Router

Route Handler Implementation:

// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'

// ✅ Correct: Named exports for HTTP methods
export async function GET(request: NextRequest) {
  try {
    const supabase = createClient()
    const { data, error } = await supabase
      .from('products')
      .select('*')
    
    if (error) throw error
    
    return NextResponse.json({ data })
  } catch (error) {
    console.error('API Error:', error)
    return NextResponse.json(
      { error: 'Failed to fetch products' },
      { status: 500 }
    )
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const supabase = createClient()
    
    const { data, error } = await supabase
      .from('products')
      .insert(body)
      .select()
    
    if (error) throw error
    
    return NextResponse.json({ data }, { status: 201 })
  } catch (error) {
    console.error('API Error:', error)
    return NextResponse.json(
      { error: 'Failed to create product' },
      { status: 500 }
    )
  }
}

// ❌ Wrong: Default export (Pages Router style)
// export default function handler(req, res) { ... }

Dynamic Route Parameters:

// app/api/products/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const productId = params.id
    
    // Validate parameter
    if (!productId || typeof productId !== 'string') {
      return NextResponse.json(
        { error: 'Invalid product ID' },
        { status: 400 }
      )
    }
    
    const supabase = createClient()
    const { data, error } = await supabase
      .from('products')
      .select('*')
      .eq('id', productId)
      .single()
    
    if (error) {
      return NextResponse.json(
        { error: 'Product not found' },
        { status: 404 }
      )
    }
    
    return NextResponse.json({ data })
  } catch (error) {
    console.error('API Error:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

Issue: CORS errors

Symptoms:

  • Cross-origin request blocked
  • Preflight request failures
  • Browser security errors

Solutions:

// Method 1: Add CORS headers to specific routes
export async function GET(request: NextRequest) {
  const data = await fetchData()
  
  return new NextResponse(JSON.stringify(data), {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Content-Type': 'application/json',
    },
  })
}

export async function OPTIONS(request: NextRequest) {
  return new NextResponse(null, {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  })
}

// Method 2: Use middleware for global CORS
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  // Handle CORS
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const response = NextResponse.next()
    
    response.headers.set('Access-Control-Allow-Origin', '*')
    response.headers.set(
      'Access-Control-Allow-Methods',
      'GET, POST, PUT, DELETE, OPTIONS'
    )
    response.headers.set(
      'Access-Control-Allow-Headers',
      'Content-Type, Authorization'
    )
    
    return response
  }
  
  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*'
}

// Method 3: CORS utility function
function corsResponse(data: any, status: number = 200) {
  return new NextResponse(JSON.stringify(data), {
    status,
    headers: {
      'Access-Control-Allow-Origin': process.env.ALLOWED_ORIGIN || '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Content-Type': 'application/json',
    },
  })
}

// Usage
export async function GET(request: NextRequest) {
  try {
    const data = await fetchData()
    return corsResponse({ data })
  } catch (error) {
    return corsResponse({ error: 'Failed to fetch data' }, 500)
  }
}

Issue: Request/Response handling errors

Symptoms:

  • JSON parsing errors
  • Request body not accessible
  • Response formatting issues

Solutions:

// ✅ Proper request handling
export async function POST(request: NextRequest) {
  try {
    // Check content type
    const contentType = request.headers.get('content-type')
    if (!contentType?.includes('application/json')) {
      return NextResponse.json(
        { error: 'Content-Type must be application/json' },
        { status: 400 }
      )
    }
    
    // Parse JSON with error handling
    let body
    try {
      body = await request.json()
    } catch (error) {
      return NextResponse.json(
        { error: 'Invalid JSON in request body' },
        { status: 400 }
      )
    }
    
    // Validate required fields
    const { name, email } = body
    if (!name || !email) {
      return NextResponse.json(
        { error: 'Name and email are required' },
        { status: 400 }
      )
    }
    
    // Process request
    const result = await processData(body)
    
    return NextResponse.json(
      { 
        message: 'Success',
        data: result,
        timestamp: new Date().toISOString()
      },
      { status: 201 }
    )
    
  } catch (error) {
    console.error('API Error:', error)
    
    // Don't expose internal errors
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

// ✅ Handle different content types
export async function POST(request: NextRequest) {
  const contentType = request.headers.get('content-type')
  
  let body
  if (contentType?.includes('application/json')) {
    body = await request.json()
  } else if (contentType?.includes('multipart/form-data')) {
    body = await request.formData()
  } else if (contentType?.includes('application/x-www-form-urlencoded')) {
    const formData = await request.formData()
    body = Object.fromEntries(formData)
  } else {
    body = await request.text()
  }
  
  // Process based on content type
  return NextResponse.json({ received: body })
}

// ✅ Query parameter handling
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url)
  
  // Get query parameters
  const page = parseInt(searchParams.get('page') || '1')
  const limit = parseInt(searchParams.get('limit') || '10')
  const search = searchParams.get('search') || ''
  
  // Validate parameters
  if (page < 1 || limit < 1 || limit > 100) {
    return NextResponse.json(
      { error: 'Invalid pagination parameters' },
      { status: 400 }
    )
  }
  
  const data = await fetchPaginatedData({ page, limit, search })
  
  return NextResponse.json({
    data: data.items,
    pagination: {
      page,
      limit,
      total: data.total,
      totalPages: Math.ceil(data.total / limit)
    }
  })
}

Error Boundary Implementation

Custom Error Boundaries

// components/ErrorBoundary.tsx
'use client'

import React, { Component, ErrorInfo, ReactNode } from 'react'

interface Props {
  children: ReactNode
  fallback?: ReactNode
  onError?: (error: Error, errorInfo: ErrorInfo) => void
}

interface State {
  hasError: boolean
  error?: Error
}

export class ErrorBoundary extends Component<Props, State> {
  public state: State = {
    hasError: false
  }

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

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('ErrorBoundary caught an error:', error, errorInfo)
    
    // Call custom error handler
    this.props.onError?.(error, errorInfo)
    
    // Send to error reporting service
    this.reportError(error, errorInfo)
  }

  private reportError = (error: Error, errorInfo: ErrorInfo) => {
    // Send to Sentry, LogRocket, or other error reporting service
    if (typeof window !== 'undefined') {
      // Client-side error reporting
      fetch('/api/errors', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          error: {
            message: error.message,
            stack: error.stack,
            name: error.name
          },
          errorInfo,
          url: window.location.href,
          userAgent: navigator.userAgent,
          timestamp: new Date().toISOString()
        })
      }).catch(console.error)
    }
  }

  public render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback
      }
      
      return (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <details>
            <summary>Error details</summary>
            <pre>{this.state.error?.stack}</pre>
          </details>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary
      fallback={<div>Application error occurred</div>}
      onError={(error, errorInfo) => {
        console.log('Custom error handler:', error)
      }}
    >
      <MyComponent />
    </ErrorBoundary>
  )
}

Async Error Handling

// Hook for async error handling
function useAsyncError() {
  const [, setError] = useState()
  return useCallback((error: Error) => {
    setError(() => {
      throw error
    })
  }, [])
}

// Component with async error handling
function AsyncComponent() {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const throwError = useAsyncError()
  
  const fetchData = async () => {
    try {
      setLoading(true)
      const response = await fetch('/api/data')
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }
      
      const result = await response.json()
      setData(result)
    } catch (error) {
      // This will be caught by ErrorBoundary
      throwError(error as Error)
    } finally {
      setLoading(false)
    }
  }
  
  useEffect(() => {
    fetchData()
  }, [])
  
  if (loading) return <div>Loading...</div>
  
  return <div>{JSON.stringify(data)}</div>
}

Memory Leak Prevention

Cleanup Patterns

// ✅ Proper cleanup patterns
function ComponentWithCleanup() {
  const [data, setData] = useState([])
  
  useEffect(() => {
    let mounted = true
    
    // Event listeners
    const handleResize = () => {
      if (mounted) {
        // Handle resize
      }
    }
    
    window.addEventListener('resize', handleResize)
    
    // Subscriptions
    const subscription = supabase
      .channel('products')
      .on('postgres_changes', 
        { event: '*', schema: 'public', table: 'products' }, 
        (payload) => {
          if (mounted) {
            setData(prev => [...prev, payload.new])
          }
        }
      )
      .subscribe()
    
    // Timers
    const timer = setInterval(() => {
      if (mounted) {
        // Do something
      }
    }, 1000)
    
    // Cleanup function
    return () => {
      mounted = false
      window.removeEventListener('resize', handleResize)
      subscription.unsubscribe()
      clearInterval(timer)
    }
  }, [])
  
  return <div>{/* Component content */}</div>
}

// ✅ AbortController for fetch requests
function ComponentWithFetch() {
  const [data, setData] = useState(null)
  
  useEffect(() => {
    const controller = new AbortController()
    
    async function fetchData() {
      try {
        const response = await fetch('/api/data', {
          signal: controller.signal
        })
        
        if (!response.ok) throw new Error('Fetch failed')
        
        const result = await response.json()
        setData(result)
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('Fetch error:', error)
        }
      }
    }
    
    fetchData()
    
    return () => {
      controller.abort()
    }
  }, [])
  
  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>
}

This comprehensive runtime errors guide provides solutions for the most common issues encountered during application execution, with practical examples and preventive measures.