Convention
에러 처리
올라핀테크 프론트엔드 프로젝트 내에서 지켜야하는 에러 처리 규칙을 서술합니다.
에러 처리
1. Logger 유틸리티
console 직접 사용 금지 - logger 유틸리티를 통해 모든 로그 관리
import { logger } from '@/shared/utils/logger'
// ✅ 로그 레벨
logger.info('사용자 정보 조회 성공', userData) // 모든 환경
logger.error('🐛[handleAction] error', error) // test 환경 제외
logger.debug('API 요청 데이터', requestData) // development만
logger.warn('⚠️ 캐시 만료 임박', cacheKey) // 모든 환경
// ❌ 금지
console.log('데이터:', data) // Lint Error특징:
- 환경별 출력 제어
- Lint 규칙:
'no-console': 'error'
2. Server Action 에러 처리
공통 에러 코드
// shared/lib/server-action/schema.ts
export const COMMON_ERROR_CODE = {
UNAUTHORIZED: 'UNAUTHORIZED',
UNKNOWN: 'UNKNOWN',
NETWORK_ERROR: 'NETWORK_ERROR',
INVALID_RESPONSE: 'INVALID_RESPONSE',
} as consthandleAction 패턴
// domain/auth/actions/sign-in/index.ts
'use server'
export const signIn = async (data: SignInRequest) => {
return handleAction({
signature: API_SIGNATURE.AUTH.SIGN_IN,
data,
schema: signInResponseSchema,
})
}내부 동작:
- API 호출
- Zod 스키마로 응답 검증
- 성공 시
ActionSuccess, 실패 시ActionFail반환
// ActionSuccess
{ type: 'success', data: T }
// ActionFail
{ type: 'fail', code: string, message?: string, timestamp: string }도메인별 에러 코드
// domain/auth/actions/sign-in/schema.ts
export const SIGN_IN_CODE = {
SUCCESS: 'SUCCESS',
NOT_FOUND_0001: 'NOT_FOUND_0001',
} as const
export const signInResponseSchema = z.union([
createApiResponseSchema({
dataSchema: signInSuccessDataSchema,
codeEnum: [SIGN_IN_CODE.SUCCESS],
}),
createApiResponseSchema({
dataSchema: z.null(),
codeEnum: [SIGN_IN_CODE.NOT_FOUND_0001],
}),
])3. Hook에서 에러 처리
// domain/auth/hooks/use-sign-in.ts
export const useSignIn = () => {
return useMutation({
mutationFn: async (data: SignInRequest) => {
const result = await signIn(data)
// 공통 에러
if (result.type === ACTION_TYPE.FAIL) {
return {
status: 'SERVER_ERROR',
message: '잠시 후 다시 시도해주세요.',
} as const
}
// 도메인 에러
if (result.data.meta.code === 'NOT_FOUND_0001') {
return {
status: 'NOT_FOUND_0001',
message: '로그인 정보가 일치하지 않아요.',
} as const
}
// 성공
if (result.data.meta.code === 'SUCCESS') {
trackEvent(ANALYTICS_EVENT.로그인__결과, { success: true })
return {
status: 'SUCCESS',
data: result.data.data,
} as const
}
return {
status: 'SERVER_ERROR',
message: '잠시 후 다시 시도해주세요.',
} as const
},
})
}React Query 에러 처리:
const { data, error, isError } = useQuery({
queryKey: QUERY_KEY.USER.CURRENT_USER_INFO,
queryFn: getCurrentUserInfo,
retry: 3,
onError: (error) => {
logger.error('사용자 정보 조회 실패', error)
toast.error('사용자 정보를 불러올 수 없습니다')
},
})
if (isError) {
return <ErrorView message="데이터를 불러올 수 없습니다" />
}4. 클라이언트 사용자 알림
import { toast } from 'sonner'
// ❌ 금지
alert('에러가 발생했습니다')
// ✅ Toast 사용
toast.error('에러가 발생했습니다')
toast.success('저장되었습니다')
toast.info('새 메시지가 있습니다')
toast.warning('주의가 필요합니다')
// 커스텀 옵션
toast.error('에러가 발생했습니다', {
description: '잠시 후 다시 시도해주세요.',
duration: 5000,
})5. React Error Boundary
Next.js Error Boundary
// app/error.tsx
'use client'
import { NotFoundView } from '@/shared/widgets/not-found'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return <NotFoundView />
}react-error-boundary 라이브러리
import { ErrorBoundary } from 'react-error-boundary'
function ErrorFallback({ error, resetErrorBoundary }: any) {
return (
<div role="alert">
<p>에러가 발생했습니다:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>다시 시도</button>
</div>
)
}
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>