Apply this skill when building error handling, error boundaries, logging, monitoring, or observability in a Next.js + TypeScript + Drizzle application. Triggers on requests like "handle errors", "add error boundary", "add logging", "add monitoring", "set up Sentry", "why is this failing silently", "add a 404 page", or any time you are building features that need to handle failure gracefully. Use proactively — every new feature should account for error states.
93
Quality
93%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Every operation that can fail must handle failure explicitly. Silent failures — mutations with no onError, API routes that swallow exceptions, components that render nothing on error — are bugs.
Every route segment should have its own error.tsx. One global boundary means one failure takes down the entire page.
app/(dashboard)/
├── error.tsx # Dashboard-level fallback
├── pipeline/
│ ├── page.tsx
│ └── error.tsx # Pipeline-specific error UI
├── candidates/
│ ├── page.tsx
│ └── error.tsx # Candidates-specific error UI
└── settings/
├── page.tsx
└── error.tsx # Settings-specific error UI// app/(dashboard)/pipeline/error.tsx
"use client"
export default function PipelineError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div className="flex flex-col items-center justify-center gap-4 p-8">
<h2 className="text-lg font-semibold">Something went wrong loading the pipeline</h2>
<p className="text-sm text-muted-foreground">
{process.env.NODE_ENV === 'development' ? error.message : 'An unexpected error occurred'}
</p>
<Button onClick={reset}>Try again</Button>
</div>
)
}global-error.tsx catches errors in the root layout itself — the last line of defence.
// app/global-error.tsx
"use client"
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body className="flex min-h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold">Something went wrong</h1>
<button onClick={reset} className="mt-4 rounded bg-primary px-4 py-2 text-primary-foreground">
Try again
</button>
</div>
</body>
</html>
)
}// app/not-found.tsx
export default function NotFound() {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold">404</h1>
<p className="mt-2 text-muted-foreground">Page not found</p>
<Link href="/" className="mt-4 inline-block text-primary underline">Go home</Link>
</div>
</div>
)
}Never throw to the client. Return typed results.
type ActionResult<T> =
| { success: true; data: T }
| { success: false; error: string }
export async function createUser(input: unknown): Promise<ActionResult<User>> {
const parsed = UserInsertSchema.safeParse(input)
if (!parsed.success) return { success: false, error: parsed.error.issues[0].message }
try {
const [user] = await db.insert(users).values(parsed.data).returning()
return { success: true, data: user }
} catch (error) {
logger.error('createUser failed', { error, input: parsed.data })
return { success: false, error: 'Failed to create user' }
}
}Consistent error response format across all endpoints.
type ApiError = {
error: string
code: string
details?: unknown
}
function errorResponse(message: string, code: string, status: number, details?: unknown) {
return Response.json({ error: message, code, details } satisfies ApiError, { status })
}
// Usage
export async function GET(request: Request) {
const session = await getServerSession()
if (!session) return errorResponse('Unauthorised', 'UNAUTHORIZED', 401)
const params = ListParamsSchema.safeParse(Object.fromEntries(new URL(request.url).searchParams))
if (!params.success) return errorResponse('Invalid parameters', 'VALIDATION_ERROR', 400, params.error.issues)
try {
const data = await fetchData(params.data)
return Response.json(data)
} catch (error) {
logger.error('GET /api/resource failed', { error })
return errorResponse('Internal server error', 'INTERNAL_ERROR', 500)
}
}error.message or stack traces in production responsesEvery mutation must have an onError handler that tells the user something went wrong.
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.all })
toast.success('User created')
},
onError: (error) => {
toast.error(error instanceof Error ? error.message : 'Something went wrong')
},
})const { data, isLoading, isError, error } = useQuery(userQueryOptions(id))
if (isLoading) return <Skeleton />
if (isError) return <ErrorMessage message={error.message} />
return <UserDetail user={data} />Replace console.log/console.error with structured logging.
// lib/logger.ts
type LogLevel = 'info' | 'warn' | 'error'
interface LogEntry {
level: LogLevel
message: string
timestamp: string
[key: string]: unknown
}
function log(level: LogLevel, message: string, context?: Record<string, unknown>) {
const entry: LogEntry = {
level,
message,
timestamp: new Date().toISOString(),
...context,
}
if (level === 'error') {
console.error(JSON.stringify(entry))
} else {
console.log(JSON.stringify(entry))
}
}
export const logger = {
info: (msg: string, ctx?: Record<string, unknown>) => log('info', msg, ctx),
warn: (msg: string, ctx?: Record<string, unknown>) => log('warn', msg, ctx),
error: (msg: string, ctx?: Record<string, unknown>) => log('error', msg, ctx),
}Integrate Sentry (or similar) for production error tracking.
// lib/sentry.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1, // 10% of transactions
})Not all errors are equal. Handle them differently:
| Type | Example | Response |
|---|---|---|
| Validation | Invalid form input | Show inline error, don't log |
| Auth | Expired session | Redirect to login |
| Not Found | Missing resource | Show 404 UI |
| Transient | Network timeout, DB connection | Retry with backoff, show "try again" |
| Permanent | Missing permission, deleted resource | Show error, no retry |
| Unexpected | Unhandled exception | Log + alert, show generic error |