Skip to content

feat: 에러 처리 전략 전수 점검 결과 및 개선 작업 #186

@tnemnorivnelee

Description

@tnemnorivnelee

구현할 기능

프로젝트의 에러 처리 전략(에러 페이지, 데이터 페칭, Mutation, 다이얼로그, 접근성)을 전수 점검한 결과 총 19건의 이슈를 발견했다. 이 이슈에서 우선순위별로 추적하고 개선한다.

점검 결과 요약표

1. 전역 에러 페이지 & 라우트 에러 바운더리

파일 경로 에러 유형 현재 처리 방식 문제점 심각도
src/app/global-error.tsx 루트 레이아웃 에러 파일 없음 global-error.tsx가 없어 루트 layout.tsx(QueryProvider, ToastViewport 등)에서 에러 발생 시 React 기본 에러 화면 노출 높음
src/app/error.tsx 글로벌 500 ErrorFallbackUI + reset() title이 "Not found"로 되어 있어 500 에러인데 404처럼 보임. Header isAuthenticated={false} 하드코딩 보통
src/app/not-found.tsx 글로벌 404 ErrorFallbackUI + 홈 이동 없음 없음
src/app/(with-header)/error.tsx 라우트 그룹 에러 ErrorFallbackUI + reset() 없음 없음
src/app/(with-header)/profile/error.tsx 프로필 에러 ErrorFallbackUI + reset() 없음 없음
src/app/(with-header)/profile/report/error.tsx 리포트 에러 자체 HTML/Tailwind ErrorFallbackUI 미사용, error.message 직접 노출(내부 에러 메시지 유출 가능), bg-blue-500 하드코딩으로 디자인 시스템 불일치, useEffect 로깅 없음 보통
(auth)/login/ 로그인 페이지 에러 없음 (글로벌 fallback) 전용 error.tsx 없음 — 글로벌 error.tsx로 fallback되어 Header가 비인증 상태로 렌더링됨 낮음
@modal/(.)login, @modal/(.)session/[sessionId] 모달 라우트 에러 없음 Parallel route에 error.tsx 없음 — 모달 내 에러 시 상위 바운더리로 전파 낮음

2. 데이터 페칭 에러 (useQuery / useSuspenseQuery)

파일 경로 에러 유형 현재 처리 방식 문제점 심각도
SessionList.tsx (useSuspenseSessionList) 데이터 페칭 Suspense fallback만 있음 ErrorBoundary 없음 — 세션 목록 fetch 실패 시 홈 페이지 서브트리 크래시 높음
RecommendedGrid.tsx (useSuspenseSessionList) 데이터 페칭 Suspense fallback만 있음 ErrorBoundary 없음 — 동일 이슈 높음
RecommendedSectionContent.tsx (useSuspenseMeForEdit) 데이터 페칭 Suspense fallback만 있음 ErrorBoundary 없음/me/edit 실패 시 추천 섹션 크래시 높음
SessionResultContent / ParticipantsReportContent (서버 컴포넌트) 데이터 페칭 Suspense만 감쌈 ErrorBoundary 없음result/page.tsx, reports/page.tsx에서 에러 시 페이지 크래시 높음
StatsContent / SessionHistoryContent (서버 컴포넌트) 데이터 페칭 throw new Error(...) ErrorBoundary 없음profile/report/page.tsx에서 에러 시 페이지 크래시 높음
WaitingRoomContent.tsx (useSessionDetail) 데이터 페칭 ErrorFallbackUI 렌더링 404 상태 코드 분기 없음 — 존재하지 않는 세션 접근 시 notFound() 미호출, 일반 에러 UI 표시 보통
WaitingRoomContent.tsx (useWaitingRoom) 데이터 페칭 error 상태 무시 대기실 멤버 fetch 실패 시 빈 배열로 렌더링 — 사용자 피드백 없음 보통
SessionDetailModal.tsx (useSessionDetail) 데이터 페칭 인라인 에러 UI 404 분기 없음 — notFound() 미호출 (모달 컨텍스트이므로 영향 제한적) 낮음
ProfileEditForm.tsx (useMeForEdit) 데이터 페칭 if (!profile) → 로딩 스피너 에러와 로딩 상태 미구분 — 에러 시 무한 로딩 표시 보통
ProfileSummary.tsx (useMe) 데이터 페칭 if (!profile) return null 에러 시 컴포넌트가 아예 안 보임 — silent failure 낮음
SessionPageContent.tsx (useMe) 데이터 페칭 error 무시 myMember가 undefined — 목표/할일 빈 상태로 렌더링 낮음
WaitingRoomContent.tsx (useMe) 데이터 페칭 error 무시 myTask가 null — silent failure 낮음

3. Mutation 에러

파일 경로 에러 유형 현재 처리 방식 문제점 심각도
LobbyHeader.tsx (useLeaveSession) Mutation try-catch 존재 치명적 버그: try {} 블록이 비어있음mutateAsync() 호출 누락. 나가기 기능 미작동 높음
SessionHeader.tsx (useLeaveSession) Mutation try-catch 존재 동일 버그try {} 블록 비어있음 높음
ProfileDropdown.tsx (useLogout) Mutation console.error only toast.error() 없음 — 로그아웃 실패 시 사용자에게 피드백 없음 보통
ProfileEditForm.tsx (useUpdateMe) Mutation onError + toast.error error as Error 타입 캐스팅 — ApiError status 손실, 코드베이스 패턴 불일치 낮음
useDeleteProfileImage Mutation 소비 컴포넌트 미확인 onError 핸들링 확인 불가 낮음

정상 처리 확인된 Mutation: useCreateSession, useJoinSession, useKickMembers, useToggleSubtaskCompletion, useSubmitSessionResult, useToggleMyStatus, useSendReaction, useUpdateGoal, useUpdateTodo, useDeleteTodo, useCreateTodo, useUpdateNickname, useUpdateProfileImage, useUpdateInterestCategories — 모두 toast.error + ApiError.message 패턴 사용

4. 다이얼로그 내 에러 표시

파일 경로 에러 유형 현재 처리 방식 문제점 심각도
SessionJoinModal.tsx 서버 에러 AlertIcon + red bg + fadeIn + toast.error role="alert" / aria-live 없음 — 스크린리더가 에러 인지 불가 보통
SessionHeader.tsx (LeaveConfirmDialog) 서버 에러 AlertIcon + red bg + fadeIn 에러 표시 코드가 dead code (mutation 미호출) + role="alert" 없음 높음
LobbyHeader.tsx (LeaveConfirmDialog) 서버 에러 AlertIcon + red bg + fadeIn 동일 dead code 이슈 + role="alert" 없음 높음
SessionDetailModal.tsx 데이터 페칭 에러 AlertIcon + muted 색상 + fadeIn role="alert" 없음. 에러 스타일이 다른 다이얼로그와 불일치 (red 미사용) 낮음
OnboardingModal.tsx 서버 에러 toast.error만 사용 모달 내부에 인라인 에러 UI 없음 낮음
ProfileDropdown.tsx 데이터 페칭 에러 AlertIcon + red-400 텍스트 에러 스타일이 또 다른 패턴 (3번째 변형) 낮음

5. 접근성 (Accessibility)

파일 경로 에러 유형 현재 처리 방식 문제점 심각도
OnboardingModal.tsx 접근성 <div> 풀스크린 오버레이 role="dialog", aria-modal, focus trap, ESC 핸들링 모두 없음 높음
LoginModal.tsx 접근성 네이티브 <dialog> aria-labelledby 없음. 포커스 복원 없음 보통
SessionDetailModal.tsx 접근성 네이티브 <dialog> aria-labelledby 없음. 포커스 복원 없음 보통
SessionJoinModal.tsx 접근성 네이티브 <dialog> aria-labelledby 없음, aria-label 없음. 포커스 복원 없음 보통
ChatPopup.tsx 접근성 <div> 패널 role="dialog" 없음, ESC 없음, 포커스 관리 없음 보통
Dropdown.tsx 접근성 <ul role="listbox"> ESC 후 포커스 복원 없음. aria-activedescendant 미사용 보통
LeaveConfirmDialog (양쪽) 접근성 네이티브 <dialog> aria-labelledby 없음. 포커스 복원은 구현됨 낮음
Filter 4종 접근성 <div role="dialog"> 포커스 복원 구현됨, aria-label 있음 낮음
ProfileDropdown.tsx 접근성 <div role="dialog"> + 풀 ARIA 없음 — 가장 잘 구현됨 없음

상세 작업 내용

P0 — 치명적 (즉시 수정)

  • useLeaveSession 미호출 버그 수정LobbyHeader.tsx, SessionHeader.tsxhandleLeave 함수에서 빈 try {} 블록에 await leaveSessionMutation.mutateAsync(sessionId) 호출 추가 및 성공 시 리다이렉트 처리

P1 — 높음

  • global-error.tsx 생성src/app/global-error.tsx 신규 생성. 루트 레이아웃 에러 캐치용. "use client" 필수, 자체 <html>, <body> 태그 포함 필요
  • Suspense에 ErrorBoundary 추가 — 아래 페이지들의 <Suspense> 바깥에 ErrorBoundary 래핑:
    • src/app/(with-header)/page.tsxSessionList, RecommendedGrid, RecommendedSectionContent
    • src/app/(with-header)/session/[sessionId]/result/page.tsxSessionResultContent
    • src/app/(with-header)/session/[sessionId]/reports/page.tsxParticipantsReportContent
    • src/app/(with-header)/profile/report/page.tsxStatsContent, SessionHistoryContent
  • OnboardingModal 접근성 개선role="dialog", aria-modal="true", aria-labelledby 추가. ESC 키 핸들러 추가. focus trap 구현 (모달 내부에 포커스 가두기)

P2 — 보통

  • error.tsx title 수정src/app/error.tsx의 title "Not found""Error" 또는 "문제가 발생했어요"로 변경
  • profile/report/error.tsx 통일ErrorFallbackUI 사용으로 교체, error.message 직접 노출 제거, useEffect 에러 로깅 추가
  • WaitingRoomContent 404 분기 추가useSessionDetail 에러에서 error instanceof ApiError && error.status === 404notFound() 호출 (SessionPageContent.tsx와 동일 패턴)
  • ProfileEditForm 에러/로딩 구분isError 상태 체크 추가하여 에러 시 에러 메시지 또는 ErrorFallbackUI 표시 (현재는 에러여도 무한 로딩 스피너)
  • useLogout 에러 피드백 추가ProfileDropdown.tsx에서 console.errortoast.error(message) 추가
  • 다이얼로그 서버 에러에 role="alert" 추가SessionJoinModal.tsx, LobbyHeader.tsx(LeaveConfirmDialog), SessionHeader.tsx(LeaveConfirmDialog)의 서버 에러 div에 role="alert" 속성 추가
  • <dialog> 요소에 aria-labelledby 추가LoginModal, SessionDetailModal, SessionJoinModal, LeaveConfirmDialog의 <dialog> 요소에 내부 제목 <h2>의 id와 연결
  • ChatPopup 접근성 보강role="dialog", aria-label 추가, ESC 키 핸들러 추가, 닫기 시 포커스 복원
  • Dropdown 포커스 복원 — ESC/선택 후 trigger 버튼에 포커스 복원하는 로직 추가
  • WaitingRoomContent useWaitingRoom 에러 처리useWaitingRoom 훅의 error 상태를 체크하여 멤버 목록 fetch 실패 시 사용자 피드백 제공

P3 — 낮음 (여유 시)

  • SessionDetailModal 404 분기useSessionDetail 에러에서 404 시 별도 처리 (모달 컨텍스트라 영향 제한적)
  • ProfileEditForm useUpdateMe 타입 캐스팅 통일error as Errorerror instanceof ApiError 패턴으로 변경
  • LoginModal/SessionDetailModal 포커스 복원 — Intercepting Route 모달의 구조적 한계로 router.back() 외 포커스 복원 방법 검토
  • ProfileSummary/useMe silent failure 대응 — 보조 데이터 영역에서 isError 시 최소한의 fallback UI 고려
  • 에러 UI 스타일 3종 통일SessionDetailModal(muted gray), SessionJoinModal(red-500), ProfileDropdown(red-400) 패턴을 하나로 통일

참고: 현재 코드 상세 (수정 시 참조)

useLeaveSession 버그 — 현재 코드 vs 기대 코드

현재 (LobbyHeader.tsx, SessionHeader.tsx 동일):

const handleLeave = async () => {
  setServerError(null);
  isLeavingRef.current = true;
  try {
    // 비어있음
  } catch (error: unknown) {
    isLeavingRef.current = false;
    const message = error instanceof ApiError ? error.message : DEFAULT_API_ERROR_MESSAGE;
    setServerError(message);
    toast.error(message);
  }
};

수정 후 기대:

const handleLeave = async () => {
  setServerError(null);
  isLeavingRef.current = true;
  try {
    await leaveSessionMutation.mutateAsync(sessionId);
    router.replace("/");
  } catch (error: unknown) {
    isLeavingRef.current = false;
    const message = error instanceof ApiError ? error.message : DEFAULT_API_ERROR_MESSAGE;
    setServerError(message);
    toast.error(message);
  }
};

ErrorBoundary 패턴 참고

홈 페이지 등에서 <Suspense> 바깥에 ErrorBoundary를 래핑하는 패턴:

// React의 ErrorBoundary 또는 react-error-boundary 라이브러리 사용
<ErrorBoundary fallback={<ErrorFallbackUI ... />}>
  <Suspense fallback={<Skeleton />}>
    <SuspenseComponent />
  </Suspense>
</ErrorBoundary>

global-error.tsx 패턴 참고

// src/app/global-error.tsx
"use client";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        {/* ErrorFallbackUI 또는 자체 에러 UI */}
        <h2>문제가 발생했습니다</h2>
        <button onClick={reset}>다시 시도</button>
      </body>
    </html>
  );
}

관련 파일 전체 목록

우선순위 파일 작업
P0 src/features/lobby/components/LobbyHeader.tsx mutation 호출 추가
P0 src/features/session/components/SessionHeader.tsx mutation 호출 추가
P1 src/app/global-error.tsx 신규 생성
P1 src/app/(with-header)/page.tsx ErrorBoundary 추가
P1 src/app/(with-header)/session/[sessionId]/result/page.tsx ErrorBoundary 추가
P1 src/app/(with-header)/session/[sessionId]/reports/page.tsx ErrorBoundary 추가
P1 src/app/(with-header)/profile/report/page.tsx ErrorBoundary 추가
P1 src/features/member/components/Onboarding/OnboardingModal.tsx 접근성 전면 개선
P2 src/app/error.tsx title 수정
P2 src/app/(with-header)/profile/report/error.tsx ErrorFallbackUI 통일
P2 src/features/lobby/components/WaitingRoomContent.tsx 404 분기 + useWaitingRoom 에러
P2 src/features/member/components/ProfileEditForm.tsx 에러/로딩 구분
P2 src/features/member/components/ProfileDropdown/ProfileDropdown.tsx logout toast
P2 src/features/lobby/components/SessionJoinModal.tsx role="alert"
P2 src/features/auth/components/LoginModal.tsx aria-labelledby
P2 src/features/session/components/SessionDetailModal/SessionDetailModal.tsx aria-labelledby
P2 src/features/session/components/SessionParticipantListCard/ChatPopup.tsx 접근성 보강
P2 src/components/Dropdown/Dropdown.tsx 포커스 복원

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions