Documentation Index Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt
Use this file to discover all available pages before exploring further.
Proper error handling is critical for building resilient applications. Remix provides multiple layers for handling errors at different stages of request processing.
Router-Level Error Handling
Catch errors at the router level to provide consistent error responses:
import { createRouter } from 'remix/fetch-router'
let router = createRouter ({
onError ( error , context ) {
console . error ( 'Router error:' , error )
if ( error instanceof ValidationError ) {
return Response . json (
{ error: error . message , fields: error . fields },
{ status: 400 }
)
}
if ( error instanceof NotFoundError ) {
return Response . json (
{ error: 'Resource not found' },
{ status: 404 }
)
}
// Generic error response
return Response . json (
{ error: 'Internal server error' },
{ status: 500 }
)
},
})
Middleware Error Handling
Handle errors in middleware:
import type { Middleware } from 'remix/fetch-router'
function errorHandler () : Middleware {
return async ( context , next ) => {
try {
return await next ()
} catch ( error ) {
console . error ( 'Middleware error:' , error )
if ( error instanceof SyntaxError ) {
return Response . json (
{ error: 'Invalid JSON in request body' },
{ status: 400 }
)
}
throw error // Re-throw for router-level handler
}
}
}
let router = createRouter ({
middleware: [ errorHandler ()],
})
Action-Level Error Handling
Handle errors in individual actions:
router . post ( routes . users , async ({ request }) => {
try {
let data = await request . json ()
// Validate input
if ( ! data . email || ! data . name ) {
return Response . json (
{ error: 'Email and name are required' },
{ status: 400 }
)
}
// Create user
let user = await db . create ( users , data )
return Response . json ( user , { status: 201 })
} catch ( error ) {
if ( error . code === 'UNIQUE_CONSTRAINT' ) {
return Response . json (
{ error: 'Email already exists' },
{ status: 409 }
)
}
throw error
}
})
Custom Error Classes
Create custom error classes for better error handling:
class AppError extends Error {
constructor (
message : string ,
public status : number = 500 ,
public code ?: string
) {
super ( message )
this . name = 'AppError'
}
}
class ValidationError extends AppError {
constructor (
message : string ,
public fields : Record < string , string >
) {
super ( message , 400 , 'VALIDATION_ERROR' )
this . name = 'ValidationError'
}
}
class NotFoundError extends AppError {
constructor ( resource : string ) {
super ( ` ${ resource } not found` , 404 , 'NOT_FOUND' )
this . name = 'NotFoundError'
}
}
class UnauthorizedError extends AppError {
constructor ( message = 'Unauthorized' ) {
super ( message , 401 , 'UNAUTHORIZED' )
this . name = 'UnauthorizedError'
}
}
Usage:
router . get ( routes . user , async ({ params }) => {
let user = await db . find ( users , { id: params . id })
if ( ! user ) {
throw new NotFoundError ( 'User' )
}
return Response . json ( user )
})
Validation Errors
Handle validation errors with detailed field information:
import { parse } from 'remix/data-schema'
import { string , object } from 'remix/data-schema'
let userSchema = object ({
name: string (). minLength ( 2 ). maxLength ( 50 ),
email: string (). email (),
age: number (). min ( 18 ). max ( 120 ),
})
router . post ( routes . users , async ({ request }) => {
let data = await request . json ()
let result = parseSafe ( userSchema , data )
if ( ! result . success ) {
let fieldErrors : Record < string , string > = {}
for ( let issue of result . issues ) {
if ( issue . path ) {
fieldErrors [ issue . path . join ( '.' )] = issue . message
}
}
throw new ValidationError ( 'Validation failed' , fieldErrors )
}
let user = await db . create ( users , result . value )
return Response . json ( user , { status: 201 })
})
Database Errors
Handle database errors gracefully:
router . post ( routes . users , async ({ request }) => {
try {
let data = await request . json ()
let user = await db . create ( users , data )
return Response . json ( user , { status: 201 })
} catch ( error : any ) {
// PostgreSQL unique constraint
if ( error . code === '23505' ) {
return Response . json (
{ error: 'Email already exists' },
{ status: 409 }
)
}
// Foreign key violation
if ( error . code === '23503' ) {
return Response . json (
{ error: 'Referenced resource does not exist' },
{ status: 400 }
)
}
throw error
}
})
AbortError Handling
Handle aborted requests:
import { AbortError } from 'remix/fetch-router'
router . get ( routes . search , async ({ url }) => {
let query = url . searchParams . get ( 'q' )
try {
let results = await fetchSearchResults ( query )
return Response . json ( results )
} catch ( error ) {
if ( error instanceof AbortError ) {
// Request was aborted, don't log as error
return new Response ( 'Request aborted' , { status: 499 })
}
throw error
}
})
Error Responses
Return consistent error response formats:
interface ErrorResponse {
error : string
code ?: string
fields ?: Record < string , string >
details ?: any
}
function errorResponse (
message : string ,
status : number ,
options ?: {
code ?: string
fields ?: Record < string , string >
details ?: any
}
) : Response {
let body : ErrorResponse = {
error: message ,
... options ,
}
return Response . json ( body , { status })
}
// Usage
return errorResponse ( 'User not found' , 404 , { code: 'NOT_FOUND' })
Error Logging
Log errors for monitoring and debugging:
function logError ( error : Error , context : any ) {
console . error ( 'Error:' , {
message: error . message ,
stack: error . stack ,
url: context . url ,
method: context . method ,
timestamp: new Date (). toISOString (),
})
// Send to error tracking service (e.g., Sentry, Rollbar)
// errorTracker.captureException(error, { extra: context })
}
let router = createRouter ({
onError ( error , context ) {
logError ( error , context )
return errorResponse ( 'Internal server error' , 500 )
},
})
Environment-Specific Errors
Provide different error details based on environment:
let isDevelopment = process . env . NODE_ENV === 'development'
let router = createRouter ({
onError ( error , context ) {
logError ( error , context )
if ( isDevelopment ) {
// Detailed errors in development
return Response . json (
{
error: error . message ,
stack: error . stack ,
context: {
url: context . url ,
method: context . method ,
},
},
{ status: 500 }
)
}
// Generic errors in production
return errorResponse ( 'Internal server error' , 500 )
},
})
Best Practices
Always handle errors at multiple levels
Use custom error classes for clarity
Log errors with context for debugging
Return consistent error response formats
Don’t expose sensitive information in production
Use appropriate HTTP status codes
Validate input early
Handle database errors specifically
Fetch Router Router error handling options
Data Schema Input validation with schemas