-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
구현할 기능
프로젝트의 에러 처리 전략(에러 페이지, 데이터 페칭, 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.tsx의handleLeave함수에서 빈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.tsx—SessionList,RecommendedGrid,RecommendedSectionContentsrc/app/(with-header)/session/[sessionId]/result/page.tsx—SessionResultContentsrc/app/(with-header)/session/[sessionId]/reports/page.tsx—ParticipantsReportContentsrc/app/(with-header)/profile/report/page.tsx—StatsContent,SessionHistoryContent
-
OnboardingModal접근성 개선 —role="dialog",aria-modal="true",aria-labelledby추가. ESC 키 핸들러 추가. focus trap 구현 (모달 내부에 포커스 가두기)
P2 — 보통
-
error.tsxtitle 수정 —src/app/error.tsx의 title"Not found"→"Error"또는"문제가 발생했어요"로 변경 -
profile/report/error.tsx통일 —ErrorFallbackUI사용으로 교체,error.message직접 노출 제거,useEffect에러 로깅 추가 -
WaitingRoomContent404 분기 추가 —useSessionDetail에러에서error instanceof ApiError && error.status === 404시notFound()호출 (SessionPageContent.tsx와 동일 패턴) -
ProfileEditForm에러/로딩 구분 —isError상태 체크 추가하여 에러 시 에러 메시지 또는ErrorFallbackUI표시 (현재는 에러여도 무한 로딩 스피너) -
useLogout에러 피드백 추가 —ProfileDropdown.tsx에서console.error→toast.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 버튼에 포커스 복원하는 로직 추가 -
WaitingRoomContentuseWaitingRoom 에러 처리 —useWaitingRoom훅의error상태를 체크하여 멤버 목록 fetch 실패 시 사용자 피드백 제공
P3 — 낮음 (여유 시)
-
SessionDetailModal404 분기 —useSessionDetail에러에서 404 시 별도 처리 (모달 컨텍스트라 영향 제한적) -
ProfileEditFormuseUpdateMe 타입 캐스팅 통일 —error as Error→error instanceof ApiError패턴으로 변경 -
LoginModal/SessionDetailModal포커스 복원 — Intercepting Route 모달의 구조적 한계로router.back()외 포커스 복원 방법 검토 -
ProfileSummary/useMesilent 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 |
포커스 복원 |
Reactions are currently unavailable