Allra Fintech
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 const

handleAction 패턴

// 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,
  })
}

내부 동작:

  1. API 호출
  2. Zod 스키마로 응답 검증
  3. 성공 시 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>