API routes and server actions that swallow exceptions without logging leave you with no server-side record of what failed. CWE-778 (Insufficient Logging) and CWE-391 (Unchecked Error Condition) both apply — a catch block that returns a 500 response without a log call means the failure is invisible outside of user complaints. ISO 25010 reliability.fault-tolerance requires that fault events be recorded. For Next.js server actions specifically, an unhandled throw produces an opaque digest hash in production — the error is invisible even to the framework's own logging. Without server-side logs, debugging production incidents requires guesswork from user descriptions alone.
High because API errors with no server-side log record make production debugging dependent on user reports rather than observable system state.
Add a consistent logging pattern to every API route catch block, capturing the full error object (not just the message).
export async function POST(request: Request) {
try {
// handler logic
} catch (error) {
console.error('[POST /api/your-route]', {
error,
message: error instanceof Error ? error.message : 'Unknown error',
})
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
For tRPC, configure a global error formatter in src/server/trpc.ts so all procedures log consistently. For server actions, wrap the action body in try/catch — Next.js will otherwise swallow thrown errors into opaque digest hashes in production, producing no actionable log.
ID: saas-error-handling.error-reporting.api-errors-logged-server
Severity: high
What to look for: Examine API route handlers (app/api/**/route.ts, pages/api/**/*.ts, server actions, tRPC procedures, or equivalent). For each catch block or error handler: (1) Is there a console.error(), logger.error(), or equivalent server-side logging call before returning the error response? (2) Does the log call include the error object (stack trace) and any available context (route, method, user ID if available)? Look for patterns where errors are silently swallowed — catch (error) { return NextResponse.json({ error: 'Failed' }) } with no logging. Also check for global API error middleware or tRPC error formatters. Server actions: Next.js server actions ('use server' files and inline async function calls in server components) are mutation handlers — examine these separately from API routes. A server action that throws without a try/catch will cause Next.js to swallow the error into a digest hash in production, producing no useful log. Check server action files for try/catch with logging.
Pass criteria: Count all API route handlers and server actions in the project. Pass if at least 90% of catch blocks in API routes include a server-side log call that captures the error object. A centralized error handler that logs for all routes is also acceptable. Pass if a logging library (pino, winston, etc.) or Sentry's captureException is called on the server before returning the error response. Report the ratio: "X of Y API route handlers have server-side error logging."
Fail criteria: Fail if any catch blocks return an error response without logging. Fail if the only logging is a console.log that doesn't include the error object (and thus loses the stack trace). Must not pass when server actions throw without try/catch — Next.js will swallow these into opaque digest errors in production. Both missing catch blocks AND catch blocks without logging are fail conditions.
Cross-reference: For consistent error response format across routes, see the api-consistent-error-format check in this audit.
Skip (N/A) when: The project has no API routes and no server-side code. Signal: no app/api/ directory, no pages/api/ directory, no server actions file ('use server' directive absent from all files), no backend framework entry point.
Detail on fail: Name specific API route files where errors are swallowed without logging (e.g., "catch blocks in app/api/stripe/webhook/route.ts and app/api/users/[id]/route.ts return errors without console.error or logger call"). Max 500 chars.
Remediation: Silent API errors make production debugging impossible. You need a server-side record of what failed before it becomes a user complaint.
A consistent error logging pattern for Next.js API routes:
export async function POST(request: Request) {
try {
// ... handler logic
} catch (error) {
console.error('[POST /api/your-route]', {
error,
message: error instanceof Error ? error.message : 'Unknown error',
})
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
For tRPC, configure a global error formatter:
export const t = initTRPC.create({
errorFormatter({ shape, error }) {
console.error('[tRPC Error]', error)
return shape
},
})
For a deeper analysis of your overall logging and observability setup, the SaaS Logging & Monitoring Audit examines structured logging, log levels, and alerting in detail.