协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
backend-error-handling【开发与工程】Implement error-handling patterns: boundaries, toasts, one API error shape. Use when "error boundary", "error toast", or "standardize API errors". Plan-only audit → plan-error-handling. Sentry triage → debug-sentry-monitor.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Degree of freedom: MIXED. Which layer and message [HIGH freedom];
existing-type/boundary probes [LOW freedom — run exactly].
ActionResult / ApiErrorObserve:
createUserthrows PrismaP2002; UI shows a blank catch;ActionResultalready lives intypes/errors.ts. Interpret: known conflict is unmapped; no field/toast path. Classify: reuseActionResult+CONFLICT; toast the message; do not add a second error type. Verify: duplicate email returns{ success: false, error: { code: 'CONFLICT' } }; form alert shown.
ActionResult / ApiError / error.tsx before adding a typeINTERNAL_ERROR is generic; PII is not in the client messageplan-error-handling; live Sentry triage → debug-sentry-monitorLayered error handling for full-stack apps: one action-result shape, boundaries, and toasts.
Before adding ANY error handling, verify:
rg "type.*Error|interface.*Error" --type ts
rg "ActionResult|ApiError" --type ts
ls -la app/error.tsx app/global-error.tsx
rg "ErrorBoundary" --type tsx
rg "formatError|handleError|reportError" --type ts
ls -la src/lib/errors* src/lib/error* 2>/dev/null # @/lib/errors
rg "success: false|error:" src/features/*/server/ --type ts | head -10
Why: Inconsistent error handling confuses users and complicates debugging. Always follow established patterns.
ActionResult or a typed API error, never as a raw throw to the UI.Layer diagram: references/server-patterns.md §Error handling layers.
AppError { code, message, details? }; code is machine-readable, message is user-safe.ActionResult<T> is a discriminated union on success; never return { data, error } both-optional.VALIDATION_ERROR, NOT_FOUND, UNAUTHORIZED, FORBIDDEN, CONFLICT, RATE_LIMITED, INTERNAL_ERROR.// types/errors.ts
interface AppError { code: string; message: string; details?: unknown }
type ActionResult<T> =
| { success: true; data: T }
| { success: false; error: AppError }
Full ErrorCode constant: references/server-patterns.md §Standard error types.
safeParse the FormData; return VALIDATION_ERROR with flatten().fieldErrors as details.UNAUTHORIZED with a "please sign in" message.revalidatePath, return { success: true, data }.P2002 → CONFLICT with a specific message).INTERNAL_ERROR message, never the stack.} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
return { success: false, error: { code: 'CONFLICT', message: 'A user with this email already exists' } }
}
console.error('createUser error:', error)
return { success: false, error: { code: 'INTERNAL_ERROR', message: 'Something went wrong. Please try again.' } }
}
Full createUser action: references/server-patterns.md §Server Action error handling.
useActionState(createUser, null) for state; useFormStatus in a child SubmitButton for pending.state.error.details; render inline with aria-invalid and aria-describedby.role="alert" banner above the fields.useOptimistic for optimistic UI updates.Full UserForm component: references/ui-patterns.md §Form error display.
app/error.tsx per page segment: 'use client', receives { error, reset }, logs in useEffect, shows a "Try again" button.app/global-error.tsx for the root: must render its own <html> and <body>.ActionResult or TanStack Query.// app/error.tsx
'use client'
export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
useEffect(() => { console.error('Page error:', error) }, [error])
return (
<div className="flex flex-col items-center justify-center min-h-[400px]">
<h2 className="text-xl font-semibold mb-4">Something went wrong</h2>
<button onClick={reset} className="px-4 py-2 bg-primary text-primary-foreground rounded-lg">Try again</button>
</div>
)
}
Both boundaries in full: references/ui-patterns.md §React error boundaries.
{ error: { code, message, details? } } body shape for every non-2xx response.400 for VALIDATION_ERROR and INVALID_JSON (SyntaxError from request.json()), 201 on create, 500 generic.Full POST /api/products handler: references/server-patterns.md §API route error handling.
queryFn/mutationFn, throw new Error(body.error?.message ?? fallback) when !res.ok so the UI gets the server message.retry: no retry on 401/403; up to 3 attempts otherwise.onSuccess invalidates the query key and toast.success; onError → toast.error(error.message).useProducts and useCreateProduct hooks: references/ui-patterns.md §TanStack Query error handling.
ErrorState({ title, message, onRetry? }): icon, heading, message, optional retry button.error + refetch) instead of ad-hoc red text per list.const { data, error, isLoading, refetch } = useProducts()
if (error) return <ErrorState title="Failed to load products" message={error.message} onRetry={() => refetch()} />
Full ErrorState component: references/ui-patterns.md §Error state UI components.