Skip to content

Comments

[Feature] 계층적 에러 핸들 (Global/Content/Api ErrorBoundary) 도입#1214

Merged
seongwon030 merged 18 commits intodevelop-fefrom
feature/#1211-error-boundary-setup-MOA-660
Feb 25, 2026
Merged

[Feature] 계층적 에러 핸들 (Global/Content/Api ErrorBoundary) 도입#1214
seongwon030 merged 18 commits intodevelop-fefrom
feature/#1211-error-boundary-setup-MOA-660

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Feb 18, 2026

#️⃣연관된 이슈

ex) #1211

📝작업 내용

전역 에러바운더리 하나로는 어떤 부분에서 에러가 났는지 구체적으로 알기 어려웠습니다. 그래서 Api에러 -> 페이지(or컴포넌트)에러
-> 전역에러 이렇게 3개의 계층으로 나누어, 최상위로 에러가 전파되도록 설계했습니다.

1. 에러 바운더리 계층화

  • BaseErrorBoundary: 공통 로직(에러 캐치, 리셋, 상태 관리)을 담당하는 클래스 컴포넌트 구현.

  • GlobalErrorBoundary (Level 1):

    • 앱 최상위 래퍼.
    • Sentry와 연동하여 프로덕션 에러 로깅 담당.
  • ContentErrorBoundary (Level 2):

    • App.tsx의 각 라우트 단위 래퍼.
    • 페이지 진입 실패 시 헤더/푸터 등 레이아웃은 유지하고 콘텐츠 영역만 에러 UI 표시.
    • 라우트 변경(페이지 이동) 시 에러 상태 자동 리셋.
  • ApiErrorBoundary (Level 3):

    • 특정 데이터 페칭 컴포넌트 단위 래퍼.
    • HTTP 상태 코드(404, 403, 500 등)에 따른 맞춤형 에러 메시지 및 '다시 시도' 기능

2. 커스텀 에러 클래스 정의

  • HttpError: 표준 HTTP 에러 (status, statusText 포함).
  • ApiError: 백엔드 응답(errorCode, message 등)을 포함하는 확장 클래스.
  • NetworkError: 네트워크 연결 실패 시 사용.

3. 유틸리티 및 설정 개선

  • apiHelpers.ts: API 응답 실패 시 단순 Error 대신 ApiError를 throw하도록 변경하여 상세 정보 전달.
  • initSDK.ts: 개발 환경(NODE_ENV=development)에서 불필요한 Sentry 전송을 막고, 필요시 VITE_ENABLE_SENTRY_IN_DEV=true로 활성화할 수 있도록 설정 개선.

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • 새로운 기능

    • 페이지별(로컬) 오류 경계 추가로 특정 경로의 오류가 해당 화면에만 국한됩니다.
    • 공통 베이스 오류 경계와 API/콘텐츠 전용 오류 화면(재시도·홈 복구 포함) 추가.
    • HTTP/API/네트워크 전용 오류 타입 도입으로 오류 정보를 더 풍부하게 표시.
    • 오류 테스트 페이지에 계층별(글로벌/콘텐츠/API) 테스트와 개발용 Sentry 토글 반영.
  • 버그 수정

    • API 실패 시 사용자에게 더 명확하고 구조화된 오류 메시지 제공.

- global 에러, content에러, api에러 테스트 추가
- 개발모드에서만 에러메세지 표시
- HttpError를 상속받아 errorCode와 data 필드를 포함하는 ApiError 클래스 정의
- resetKeys를 pathname으로 설정
- status, statusText, message 정의
- React 클래스 컴포넌트 기반의 BaseErrorBoundary 구현
- 외부에서 리셋 로직을 주입받을 수 있도록 onReset, resetKeys prop 지원
- handleResponse 유틸 함수에서 일반 Error 대신 커스텀 ApiError를 throw하도록 변경
- 백엔드 응답의 status, statusText, errorCode, message 등을 구조화하여 전달
- 최상위 GlobalBoundary 하위에 라우트 레벨의 ContentErrorBoundary 적용
- 각 페이지 단위로 에러를 격리하여 앱 전체 크래시 방지
- VITE_ENABLE_SENTRY_IN_DEV 환경변수로 개발 환경에서 Sentry 활성화 제어 기능 추가
@seongwon030 seongwon030 self-assigned this Feb 18, 2026
@seongwon030 seongwon030 added ✨ Feature 기능 개발 💻 FE Frontend labels Feb 18, 2026
@vercel
Copy link

vercel bot commented Feb 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview, Comment Feb 25, 2026 7:13am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

글로벌·페이지(콘텐츠)·API 레벨의 계층적 에러 바운더리 추가, Http/Api/Network 에러 타입 도입, App.tsx의 여러 라우트를 ContentErrorBoundary로 래핑 및 API 응답 실패 시 구조화된 ApiError throw로 변경했습니다.

Changes

Cohort / File(s) Summary
앱 라우트 통합
frontend/src/App.tsx
다수의 Route를 ContentErrorBoundary로 래핑하고 최상위에 기존의 GlobalBoundary 유지.
API 응답 처리
frontend/src/apis/utils/apiHelpers.ts
비정상 응답에서 generic Error 대신 ApiError 생성/throw로 변경(응답 JSON 파싱, errorCode·message 추출 포함).
에러 타입 정의
frontend/src/errors/...
frontend/src/errors/HttpError.ts, frontend/src/errors/ApiError.ts, frontend/src/errors/NetworkError.ts, frontend/src/errors/index.ts
HttpError/ApiError/NetworkError 클래스 추가 및 중앙 re-export.
기본 에러 바운더리 인프라
frontend/src/components/common/ErrorBoundary/BaseErrorBoundary.tsx, frontend/src/components/common/ErrorBoundary/index.ts
재사용 가능한 BaseErrorBoundary 추가 및 ErrorBoundary 모듈의 명시적 export 재구성(타입 export 포함).
콘텐츠 레벨 바운더리 및 폴백
frontend/src/components/common/ErrorBoundary/ContentError/...
ContentErrorBoundary.tsx, ContentErrorFallback.tsx, ContentErrorFallback.styles.ts
페이지 수준 ContentErrorBoundary와 폴백 UI/스타일 추가(현재 경로를 resetKey로 사용, 재시도·홈 버튼 포함).
API 레벨 바운더리 및 폴백
frontend/src/components/common/ErrorBoundary/ApiError/...
ApiErrorBoundary.tsx, ApiErrorFallback.tsx, ApiErrorFallback.styles.ts
데이터 페치 전용 ApiErrorBoundary와 HTTP/Network 오류 유형별 폴백 UI·스타일 추가.
글로벌 경로 수정
frontend/src/components/common/ErrorBoundary/GlobalError/GlobalBoundary.tsx
Spinner 상대경로 조정(경로 깊이 변경).
에러 테스트 페이지
frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx, .../ErrorTestPage.styles.ts
글로벌/콘텐츠/API 계층별 테스트 컴포넌트 및 Sentry 수동 테스트 UI 추가, 스타일 업데이트.
Sentry 초기화 변경
frontend/src/utils/initSDK.ts
개발 환경에서 Sentry 활성화 토글(VITE_ENABLE_SENTRY_IN_DEV) 추가 및 init 옵션에 environment 필드 설정.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Router
    participant ContentBoundary as ContentErrorBoundary
    participant Page as PageComponent
    participant ApiBoundary as ApiErrorBoundary
    participant API as BackendAPI
    participant GlobalBoundary as GlobalBoundary

    User->>Router: 경로 요청
    Router->>ContentBoundary: Route 렌더링 (페이지별 boundary)
    ContentBoundary->>Page: children 렌더
    Page->>ApiBoundary: 데이터 페치 컴포넌트 렌더
    ApiBoundary->>API: fetch 요청
    API-->>ApiBoundary: 200 / 4xx / 5xx / 네트워크 오류
    alt 오류 응답 또는 네트워크 에러
        ApiBoundary-->>ContentBoundary: throw ApiError / NetworkError
        ContentBoundary->>GlobalBoundary: 버블업(처리되지 않으면)
    else 정상 응답
        ApiBoundary-->>Page: 데이터 반환
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lepitaaar
  • oesnuj
  • suhyun113
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 핵심 변경사항인 계층적 에러 바운더리 도입(Global/Content/Api ErrorBoundary)을 명확하고 간결하게 설명합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#1211-error-boundary-setup-MOA-660

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (10)
frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx (1)

4-4: 불필요하게 상위 디렉토리를 경유하는 임포트 경로

이 파일이 이미 ContentError/ 디렉토리 안에 있으므로, ../ContentError/ContentErrorFallback 대신 ./ContentErrorFallback이 적절합니다.

♻️ 수정 제안
-import ContentErrorFallback from '../ContentError/ContentErrorFallback';
+import ContentErrorFallback from './ContentErrorFallback';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx`
at line 4, The import in ContentErrorBoundary.tsx unnecessarily traverses up one
level; update the import for ContentErrorFallback in the ContentErrorBoundary
component from '../ContentError/ContentErrorFallback' to a relative import
'./ContentErrorFallback' so the module resolves from the current ContentError
directory (locate the import statement at the top of ContentErrorBoundary.tsx
and replace the path accordingly).
frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorFallback.styles.ts (1)

3-109: 테마 시스템 미사용: 하드코딩된 색상값을 디자인 토큰으로 대체 필요

이 파일의 모든 색상값(#ff5414, #111111, #787878, #f5f5f5, #ebebeb 등)이 frontend/src/styles/theme/colors.ts에 이미 정의되어 있습니다. 예를 들어 #ff5414colors.primary[900], #787878colors.gray[700]으로 정의되어 있습니다.

또한 ApiErrorFallback.styles.ts와 GlobalErrorFallback.styles.ts 모두 동일한 구조(Container, Content, IconWrapper, Title, Message, ErrorDetails, ButtonGroup, BaseButton)를 가지고 있어 상당한 스타일 중복이 있습니다.

테마 토큰을 활용하고 공통 스타일을 추출하면 유지보수성과 일관성을 크게 개선할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorFallback.styles.ts`
around lines 3 - 109, The styles file uses hardcoded hex colors and duplicates
layout across many components; replace all literal color values in Container,
Content, IconWrapper, Title, Message, ErrorDetails, ErrorMessage, ButtonGroup,
BaseButton, PrimaryButton, and SecondaryButton with the corresponding tokens
from frontend/src/styles/theme/colors.ts (e.g., use colors.primary[900] for
`#ff5414`, colors.gray[700] for `#787878`, colors.neutral[...] for `#f5f5f5/`#ebebeb,
etc.), and refactor the repeated structure by extracting the shared styled
components (Container, Content, IconWrapper, Title, Message, ErrorDetails,
ButtonGroup, BaseButton) into a new common styles module that
ApiErrorFallback.styles.ts, GlobalErrorFallback.styles.ts, and this file import
from to remove duplication and ensure consistent theme usage.
frontend/src/App.tsx (1)

50-121: <Route>마다 <ContentErrorBoundary>를 반복 래핑하고 있어 중복이 많습니다.

Layout Route 패턴을 활용하면 중복을 크게 줄일 수 있습니다. 예를 들어:

Layout Route 패턴 예시
// ContentErrorLayout.tsx
const ContentErrorLayout = () => (
  <ContentErrorBoundary>
    <Outlet />
  </ContentErrorBoundary>
);

// App.tsx
<Routes>
  <Route element={<ContentErrorLayout />}>
    <Route path='/' element={<MainPage />} />
    <Route path='/club/:clubId' element={<LegacyClubDetailPage />} />
    {/* ...나머지 라우트 */}
  </Route>
  {/* ContentErrorBoundary가 필요 없는 라우트 */}
  <Route path='/admin/login' element={<LoginTab />} />
</Routes>

단, Layout Route로 감싸면 ContentErrorBoundary 인스턴스가 하나로 공유되므로, 라우트 변경 시 resetKeys 기반 리셋이 정상 동작하는지 확인이 필요합니다. 현재 구조에서는 각 라우트마다 별도 인스턴스가 생성되어 언마운트/리마운트로 자연스럽게 리셋됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.tsx` around lines 50 - 121, Refactor to eliminate repetitive
wrapping of routes with ContentErrorBoundary by introducing a layout route
component (e.g., ContentErrorLayout) that renders <ContentErrorBoundary> around
an <Outlet />, then wrap all routes that currently use ContentErrorBoundary
(paths rendering MainPage, LegacyClubDetailPage, ClubDetailPage, IntroducePage,
AdminRoutes inside AdminClubProvider/PrivateRoute, ApplicationFormPage,
ClubUnionPage) inside a single <Route element={<ContentErrorLayout />}> within
your <Routes>. Keep routes that must remain unwrapped (e.g., LoginTab at
'/admin/login') outside the layout, and after implementing, verify that
ContentErrorBoundary’s reset behavior (resetKeys or unmount/remount semantics)
still works as expected when navigating between these routes.
frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorFallback.tsx (2)

41-45: error null 체크가 불필요합니다.

ErrorFallbackProps에서 errorError 타입으로 non-nullable하게 정의되어 있으므로, isDev && error 조건에서 error 체크는 항상 truthy입니다. isDev만으로 충분합니다.

제안
-        {isDev && error && (
+        {isDev && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorFallback.tsx`
around lines 41 - 45, The conditional currently checks both isDev and error even
though ErrorFallbackProps types error as non-nullable; remove the redundant
error check and render the debug block guarded only by isDev (i.e., change the
condition to isDev && ...), keeping the existing children Styled.ErrorDetails
and Styled.ErrorMessage that reference error.message so the non-nullable error
from ErrorFallbackProps is used directly.

4-18: WarningIconApiErrorFallbackAlertIcon이 거의 동일한 패턴입니다.

두 폴백 컴포넌트 모두 인라인 SVG 아이콘을 각각 정의하고 있습니다. 공통 아이콘 컴포넌트로 추출하거나, 프로젝트에서 사용 중인 아이콘 시스템으로 통합하면 중복을 줄일 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorFallback.tsx`
around lines 4 - 18, WarningIcon and the AlertIcon used in ApiErrorFallback are
duplicate inline SVGs; extract a shared icon component (e.g., CommonAlertIcon or
AlertIcon) and replace both WarningIcon and the inline AlertIcon in
ApiErrorFallback to import and use that single component so the SVG lives in one
place and the two fallbacks consume the shared symbol. Locate the WarningIcon
function and the AlertIcon JSX inside ApiErrorFallback, move the SVG markup into
a new exported component (or into the project's icon system) and update both
references to use the new component.
frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.tsx (1)

77-83: error null 체크가 불필요합니다.

ContentErrorFallback과 동일하게, ErrorFallbackProps에서 error는 non-nullable Error 타입이므로 isDev만으로 충분합니다.

제안
-        {isDev && error && (
+        {isDev && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.tsx`
around lines 77 - 83, Remove the redundant null check on error in
ApiErrorFallback: inside the ApiErrorFallback component where the JSX currently
guards with "{isDev && error && (...)}", change the condition to only "{isDev &&
(...)}" because ErrorFallbackProps types error as a non-nullable Error (same
pattern as ContentErrorFallback); update the referenced block that renders
Styled.ErrorDetails / Styled.ErrorDetailsMessage accordingly so it relies on
error being present per the types.
frontend/src/errors/ApiError.ts (1)

3-15: 생성자 파라미터 순서 개선을 고려해 주세요.

message가 마지막 파라미터인데, errorCodedata 없이 message만 전달하려면 중간 인자에 undefined/null을 채워야 합니다. 실제로 ErrorTestPage.tsx에서 new ApiError(404, 'Not Found', 'RESOURCE_NOT_FOUND', null, '...')처럼 datanull을 명시적으로 전달하고 있습니다.

옵션 객체 패턴을 사용하면 호출부가 더 명확해집니다:

옵션 객체 패턴 예시
+interface ApiErrorOptions {
+  errorCode?: string;
+  data?: unknown;
+  message?: string;
+}
+
 export class ApiError extends HttpError {
+  public readonly errorCode?: string;
+  public readonly data?: unknown;
+
   constructor(
     status: number,
     statusText: string,
-    public readonly errorCode?: string,
-    public readonly data?: unknown,
-    message?: string,
+    options?: ApiErrorOptions,
   ) {
-    super(status, statusText, message);
+    super(status, statusText, options?.message);
     this.name = 'ApiError';
+    this.errorCode = options?.errorCode;
+    this.data = options?.data;
     Object.setPrototypeOf(this, ApiError.prototype);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/errors/ApiError.ts` around lines 3 - 15, Change ApiError to use
an options-object pattern so callers can pass message without needing
placeholders: update the constructor signature of ApiError to something like
constructor(status: number, statusText: string, message?: string, options?: {
errorCode?: string; data?: unknown }) and map options.errorCode/options.data to
the class readonly fields; keep calling super(status, statusText, message),
maintain this.name and Object.setPrototypeOf(this, ApiError.prototype), and
update callers (e.g., ErrorTestPage.tsx) to use new ApiError(404, 'Not Found',
'human message', { errorCode: 'RESOURCE_NOT_FOUND', data: null }) instead of
stuffing undefined into positional args.
frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx (1)

109-122: Sentry 테스트 함수에서 alert()를 사용하고 있습니다.

개발 전용 테스트 페이지이므로 큰 문제는 아니지만, alert()는 메인 스레드를 블로킹합니다. 향후에는 토스트 메시지나 페이지 내 알림으로 교체하는 것이 더 나은 UX를 제공합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx` around lines 109 - 122,
testSentryCapture and testSentryMessage use blocking alert() calls; replace
those alert(...) calls with the app's non-blocking toast/notification API (e.g.,
useToast, ToastContext, enqueueSnackbar, or the project's Notification
component) so UX isn't blocked; keep the Sentry.captureException/captureMessage
calls intact, then call the toast with a success/info message (and appropriate
severity) instead of alert, and ensure any imports/hooks needed for the toast
are added to ErrorTestPage.tsx and used inside testSentryCapture and
testSentryMessage.
frontend/src/components/common/ErrorBoundary/index.ts (1)

1-8: 명명 일관성: GlobalBoundary vs ContentErrorBoundary / ApiErrorBoundary

ContentErrorBoundary, ApiErrorBoundary에는 Error가 포함되어 있으나 GlobalBoundary(Line 3)에는 빠져 있습니다. 3계층 구조의 일관성을 위해 GlobalErrorBoundary로 통일하는 것을 고려해 주세요. 같은 모듈의 폴백 컴포넌트인 GlobalErrorFallback도 "Error"를 포함하고 있으므로 경계 컴포넌트도 맞춰주면 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/common/ErrorBoundary/index.ts` around lines 1 - 8,
The export name is inconsistent: change the re-export of GlobalBoundary to
GlobalErrorBoundary to match the other boundary names and its fallback
GlobalErrorFallback; update the export statement currently exporting default as
GlobalBoundary in this file to export default as GlobalErrorBoundary (symbol:
GlobalErrorBoundary) and ensure the module path './GlobalError/GlobalBoundary'
still points to the component (or rename the component's default export in that
module if necessary) so all boundary exports are consistently named
(ContentErrorBoundary, ApiErrorBoundary, GlobalErrorBoundary).
frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.styles.ts (1)

1-82: 하드코딩된 컬러값을 테마 토큰으로 교체하세요.

프로젝트의 테마 시스템(@/styles/theme/colors)이 잘 정의되어 있으나, 이 파일은 직접 색상값을 하드코딩하고 있습니다. 다음과 같이 매핑하여 교체해주세요:

  • #989898theme.colors.gray[600]
  • #333333theme.colors.gray[900]
  • #ebebebtheme.colors.gray[300]
  • #f8f8f8theme.colors.gray[100]
  • #ff5414theme.colors.primary[900]
  • #dcdcdctheme.colors.gray[400]

또한 font-family: 'Pretendard'는 Global.styles.ts의 createGlobalStyle에서 이미 전역으로 선언되어 있으므로, RetryButton(Line 72)에서 중복 선언을 제거하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.styles.ts`
around lines 1 - 82, Replace hardcoded hex colors in this styled-components file
with the theme tokens and remove the duplicate font-family on RetryButton:
update IconWrapper color to theme.colors.gray[600], Title color to
theme.colors.gray[900], ErrorDetails border to theme.colors.gray[300],
ErrorDetails background to theme.colors.gray[100], ErrorDetailsMessage color to
theme.colors.primary[900], and RetryButton border color to
theme.colors.gray[400]; ensure you reference the theme (props.theme or
styled-components theme) within
Container/Content/IconWrapper/Title/Message/ErrorDetails/ErrorDetailsMessage/RetryButton
and delete the font-family declaration from RetryButton since global styles
already set it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.styles.ts`:
- Around line 62-82: RetryButton is missing a :focus-visible state so keyboard
users get no visual focus cue; update the styled component RetryButton to add a
:focus-visible rule that provides a clear visible outline (or box-shadow and
adjusted border-color) consistent with :hover/:active styles and ensure it does
not appear on mouse focus only (use :focus-visible pseudo-class). Modify the
RetryButton styled definition to include the :focus-visible selector and mirror
the component's visual language (e.g., solid outline color, increased z-index if
needed) so keyboard navigation accessibility is restored.

In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx`:
- Around line 223-255: The ApiErrorBoundary is not resetting when the external
"Api 리셋" button updates apiErrorType because the boundary lacks resetKeys;
update the usage around ApiErrorBoundary (wrapping ApiErrorTest) to pass
resetKeys={[apiErrorType]} so the boundary resets whenever apiErrorType changes;
for ContentErrorBoundary, add support for an optional resetKeys prop in the
ContentErrorBoundary component (in addition to its existing pathname-based
reset) and then pass resetKeys={[contentError]} from the page when you want the
boundary to auto-reset on contentError changes.

In `@frontend/src/utils/initSDK.ts`:
- Line 64: The environment field is always falling back to 'production' because
import.meta.env.NODE_ENV is undefined; change the assignment in initSDK.ts (the
environment property) to use the Vite mode variable first (e.g.,
import.meta.env.MODE) and fall back to import.meta.env.NODE_ENV and then
'production' so dev builds are labeled correctly (for example: environment:
import.meta.env.MODE || import.meta.env.NODE_ENV || 'production').
- Around line 45-52: The environment guard is using import.meta.env.NODE_ENV
which Vite does not populate, so replace that check with Vite-provided variables
(e.g., import.meta.env.DEV or import.meta.env.MODE === 'development') to
correctly detect dev mode; update the conditional around enableInDev (the block
that reads import.meta.env.VITE_ENABLE_SENTRY_IN_DEV and the if statement
currently referencing NODE_ENV) so it returns early only when running in
development according to import.meta.env.DEV (or MODE) and enableInDev is false,
preserving the existing console message flow.

---

Nitpick comments:
In `@frontend/src/App.tsx`:
- Around line 50-121: Refactor to eliminate repetitive wrapping of routes with
ContentErrorBoundary by introducing a layout route component (e.g.,
ContentErrorLayout) that renders <ContentErrorBoundary> around an <Outlet />,
then wrap all routes that currently use ContentErrorBoundary (paths rendering
MainPage, LegacyClubDetailPage, ClubDetailPage, IntroducePage, AdminRoutes
inside AdminClubProvider/PrivateRoute, ApplicationFormPage, ClubUnionPage)
inside a single <Route element={<ContentErrorLayout />}> within your <Routes>.
Keep routes that must remain unwrapped (e.g., LoginTab at '/admin/login')
outside the layout, and after implementing, verify that ContentErrorBoundary’s
reset behavior (resetKeys or unmount/remount semantics) still works as expected
when navigating between these routes.

In
`@frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.styles.ts`:
- Around line 1-82: Replace hardcoded hex colors in this styled-components file
with the theme tokens and remove the duplicate font-family on RetryButton:
update IconWrapper color to theme.colors.gray[600], Title color to
theme.colors.gray[900], ErrorDetails border to theme.colors.gray[300],
ErrorDetails background to theme.colors.gray[100], ErrorDetailsMessage color to
theme.colors.primary[900], and RetryButton border color to
theme.colors.gray[400]; ensure you reference the theme (props.theme or
styled-components theme) within
Container/Content/IconWrapper/Title/Message/ErrorDetails/ErrorDetailsMessage/RetryButton
and delete the font-family declaration from RetryButton since global styles
already set it.

In `@frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.tsx`:
- Around line 77-83: Remove the redundant null check on error in
ApiErrorFallback: inside the ApiErrorFallback component where the JSX currently
guards with "{isDev && error && (...)}", change the condition to only "{isDev &&
(...)}" because ErrorFallbackProps types error as a non-nullable Error (same
pattern as ContentErrorFallback); update the referenced block that renders
Styled.ErrorDetails / Styled.ErrorDetailsMessage accordingly so it relies on
error being present per the types.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx`:
- Line 4: The import in ContentErrorBoundary.tsx unnecessarily traverses up one
level; update the import for ContentErrorFallback in the ContentErrorBoundary
component from '../ContentError/ContentErrorFallback' to a relative import
'./ContentErrorFallback' so the module resolves from the current ContentError
directory (locate the import statement at the top of ContentErrorBoundary.tsx
and replace the path accordingly).

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorFallback.styles.ts`:
- Around line 3-109: The styles file uses hardcoded hex colors and duplicates
layout across many components; replace all literal color values in Container,
Content, IconWrapper, Title, Message, ErrorDetails, ErrorMessage, ButtonGroup,
BaseButton, PrimaryButton, and SecondaryButton with the corresponding tokens
from frontend/src/styles/theme/colors.ts (e.g., use colors.primary[900] for
`#ff5414`, colors.gray[700] for `#787878`, colors.neutral[...] for `#f5f5f5/`#ebebeb,
etc.), and refactor the repeated structure by extracting the shared styled
components (Container, Content, IconWrapper, Title, Message, ErrorDetails,
ButtonGroup, BaseButton) into a new common styles module that
ApiErrorFallback.styles.ts, GlobalErrorFallback.styles.ts, and this file import
from to remove duplication and ensure consistent theme usage.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorFallback.tsx`:
- Around line 41-45: The conditional currently checks both isDev and error even
though ErrorFallbackProps types error as non-nullable; remove the redundant
error check and render the debug block guarded only by isDev (i.e., change the
condition to isDev && ...), keeping the existing children Styled.ErrorDetails
and Styled.ErrorMessage that reference error.message so the non-nullable error
from ErrorFallbackProps is used directly.
- Around line 4-18: WarningIcon and the AlertIcon used in ApiErrorFallback are
duplicate inline SVGs; extract a shared icon component (e.g., CommonAlertIcon or
AlertIcon) and replace both WarningIcon and the inline AlertIcon in
ApiErrorFallback to import and use that single component so the SVG lives in one
place and the two fallbacks consume the shared symbol. Locate the WarningIcon
function and the AlertIcon JSX inside ApiErrorFallback, move the SVG markup into
a new exported component (or into the project's icon system) and update both
references to use the new component.

In `@frontend/src/components/common/ErrorBoundary/index.ts`:
- Around line 1-8: The export name is inconsistent: change the re-export of
GlobalBoundary to GlobalErrorBoundary to match the other boundary names and its
fallback GlobalErrorFallback; update the export statement currently exporting
default as GlobalBoundary in this file to export default as GlobalErrorBoundary
(symbol: GlobalErrorBoundary) and ensure the module path
'./GlobalError/GlobalBoundary' still points to the component (or rename the
component's default export in that module if necessary) so all boundary exports
are consistently named (ContentErrorBoundary, ApiErrorBoundary,
GlobalErrorBoundary).

In `@frontend/src/errors/ApiError.ts`:
- Around line 3-15: Change ApiError to use an options-object pattern so callers
can pass message without needing placeholders: update the constructor signature
of ApiError to something like constructor(status: number, statusText: string,
message?: string, options?: { errorCode?: string; data?: unknown }) and map
options.errorCode/options.data to the class readonly fields; keep calling
super(status, statusText, message), maintain this.name and
Object.setPrototypeOf(this, ApiError.prototype), and update callers (e.g.,
ErrorTestPage.tsx) to use new ApiError(404, 'Not Found', 'human message', {
errorCode: 'RESOURCE_NOT_FOUND', data: null }) instead of stuffing undefined
into positional args.

In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx`:
- Around line 109-122: testSentryCapture and testSentryMessage use blocking
alert() calls; replace those alert(...) calls with the app's non-blocking
toast/notification API (e.g., useToast, ToastContext, enqueueSnackbar, or the
project's Notification component) so UX isn't blocked; keep the
Sentry.captureException/captureMessage calls intact, then call the toast with a
success/info message (and appropriate severity) instead of alert, and ensure any
imports/hooks needed for the toast are added to ErrorTestPage.tsx and used
inside testSentryCapture and testSentryMessage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx (1)

4-4: 불필요하게 우회하는 임포트 경로를 단순화하세요.

ContentErrorBoundary.tsxContentError/ 디렉터리 안에 있으므로, ../ContentError/ContentErrorFallback은 결국 ./ContentErrorFallback과 동일한 경로로 해석됩니다. 한 단계 올라갔다가 다시 같은 폴더로 내려오는 우회 경로는 혼란을 줄 수 있습니다.

♻️ 제안: 임포트 경로 단순화
-import ContentErrorFallback from '../ContentError/ContentErrorFallback';
+import ContentErrorFallback from './ContentErrorFallback';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx`
at line 4, The import in ContentErrorBoundary.tsx uses an unnecessary roundabout
path; update the import of ContentErrorFallback in ContentErrorBoundary.tsx to a
direct relative path (replace the "../ContentError/ContentErrorFallback" style
import with the local "./ContentErrorFallback" form) so it references the
sibling file in the same directory without going up and back down.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx`:
- Line 4: The import in ContentErrorBoundary.tsx uses an unnecessary roundabout
path; update the import of ContentErrorFallback in ContentErrorBoundary.tsx to a
direct relative path (replace the "../ContentError/ContentErrorFallback" style
import with the local "./ContentErrorFallback" form) so it references the
sibling file in the same directory without going up and back down.

- 개발 환경에서도 VITE_ENABLE_SENTRY_IN_DEV=true 환경변수로 Sentry를 활성화할 수 있도록 변경
- Sentry environment 설정을 import.meta.env.MODE 기반으로 명시하여 환경 구분 강
Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러 바운더리 계층으로 나눈거 수고하셨습니다 혹시나 디버깅하기좋겠는데요

@seongwon030 seongwon030 merged commit fcec375 into develop-fe Feb 25, 2026
2 of 4 checks passed
@seongwon030 seongwon030 deleted the feature/#1211-error-boundary-setup-MOA-660 branch February 25, 2026 07:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx (1)

194-196: 이전 리뷰에서 지적된 resetKeys 누락 이슈가 올바르게 해결되었습니다

  • ContentErrorBoundaryresetKeys={[contentError]} 전달 → contentError 상태 변경 시 바운더리 자동 리셋
  • ApiErrorBoundaryresetKeys={[apiErrorType]} 전달 → apiErrorType 변경 시 바운더리 자동 리셋

Also applies to: 223-225

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx` around lines 194 - 196,
The ContentErrorBoundary already correctly receives resetKeys={[contentError]},
but another ErrorBoundary instance further down (the ApiErrorBoundary at the
other occurrence) still needs the same treatment; update the second boundary
(ApiErrorBoundary) to include resetKeys={[apiErrorType]} so it resets when
apiErrorType changes, and verify the other ContentErrorBoundary occurrence also
has resetKeys={[contentError]} to ensure both boundaries reset on their
respective state changes.
🧹 Nitpick comments (3)
frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx (2)

18-51: QueryErrorResetBoundary 없이 throwOnError: true 사용 — 쿼리 에러 캐시 리셋이 보장되지 않습니다

TanStack Query에서 throwOnError를 사용하는 경우, 에러 발생 후 재렌더링 시 쿼리에 재시도를 알릴 방법이 필요합니다. QueryErrorResetBoundary 컴포넌트를 사용하면 그 범위 내의 쿼리 에러를 리셋할 수 있습니다.

현재 구조에서는 resetKeys 변경으로 에러 바운더리가 리셋되더라도 React Query 캐시의 에러 상태가 유지될 수 있습니다. 이 테스트 페이지의 경우 queryKeyerrorType이 포함되어 있고 리셋 시 enabled: false로 전환되기 때문에 동작상 문제가 없습니다만, ApiErrorBoundary 내부에서 throwOnError: true 패턴을 프로덕션에서도 사용한다면 QueryErrorResetBoundary로 래핑하는 것을 권장합니다.

♻️ 권장 패턴 예시
+import { QueryErrorResetBoundary } from '@tanstack/react-query';

-<ApiErrorBoundary resetKeys={[apiErrorType]}>
-  <ApiErrorTest errorType={apiErrorType} />
-</ApiErrorBoundary>
+<QueryErrorResetBoundary>
+  {({ reset }) => (
+    <ApiErrorBoundary resetKeys={[apiErrorType]} onReset={reset}>
+      <ApiErrorTest errorType={apiErrorType} />
+    </ApiErrorBoundary>
+  )}
+</QueryErrorResetBoundary>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx` around lines 18 - 51, The
use of useQuery inside ApiErrorTest with throwOnError: true can leave React
Query's error state uncleared; wrap the component (or the part rendering the
query) with a QueryErrorResetBoundary so errors are reset correctly on retry or
key changes instead of relying on enabled toggles—specifically, update the
component that renders ApiErrorTest (or ApiErrorBoundary if present) to include
QueryErrorResetBoundary around the useQuery consumer and ensure resetKeys
include ['api-error-test', errorType] so useQuery (with throwOnError) is reset
properly; keep the existing queryKey and enabled logic but add the boundary to
guarantee cache error reset.

282-284: window.location.href 대신 useNavigate 사용 권장

window.location.href = '/'는 전체 페이지 리로드를 유발합니다. React Router 앱에서는 useNavigate 훅을 사용하여 SPA 내비게이션을 유지하는 것이 적절합니다.

♻️ 제안 수정
+import { useNavigate } from 'react-router-dom';

 const ErrorTestPage = () => {
+  const navigate = useNavigate();
   // ...

-  <Styled.BackButton onClick={() => (window.location.href = '/')}>
+  <Styled.BackButton onClick={() => navigate('/')}>
     ← 메인 페이지로 돌아가기
   </Styled.BackButton>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx` around lines 282 - 284,
Replace the plain location assignment in the Styled.BackButton onClick with
React Router navigation: import and call the useNavigate hook (from
'react-router-dom') inside the ErrorTestPage component to get a navigate
function, then change the onClick handler on Styled.BackButton to call
navigate('/') so navigation stays within the SPA; ensure the import and the
navigate const are defined in the same component scope where Styled.BackButton
is used.
frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx (1)

4-4: ContentErrorFallback 임포트 경로를 상대 경로(./)로 단순화 권장

현재 '../ContentError/ContentErrorFallback' 경로는 상위 디렉터리로 올라간 뒤 다시 현재 디렉터리(ContentError/)로 내려옵니다. 이 파일 자체가 ContentError/ 안에 위치하므로 './ContentErrorFallback'으로 직접 참조하는 것이 더 명확합니다.

♻️ 제안 수정
-import ContentErrorFallback from '../ContentError/ContentErrorFallback';
+import ContentErrorFallback from './ContentErrorFallback';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx`
at line 4, Replace the import of ContentErrorFallback in
ContentErrorBoundary.tsx to use the local relative path instead of traversing up
then back down: change the import reference for the symbol ContentErrorFallback
from '../ContentError/ContentErrorFallback' to './ContentErrorFallback' so the
file imports directly from the same ContentError directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx`:
- Around line 194-196: The ContentErrorBoundary already correctly receives
resetKeys={[contentError]}, but another ErrorBoundary instance further down (the
ApiErrorBoundary at the other occurrence) still needs the same treatment; update
the second boundary (ApiErrorBoundary) to include resetKeys={[apiErrorType]} so
it resets when apiErrorType changes, and verify the other ContentErrorBoundary
occurrence also has resetKeys={[contentError]} to ensure both boundaries reset
on their respective state changes.

---

Nitpick comments:
In
`@frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx`:
- Line 4: Replace the import of ContentErrorFallback in ContentErrorBoundary.tsx
to use the local relative path instead of traversing up then back down: change
the import reference for the symbol ContentErrorFallback from
'../ContentError/ContentErrorFallback' to './ContentErrorFallback' so the file
imports directly from the same ContentError directory.

In `@frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx`:
- Around line 18-51: The use of useQuery inside ApiErrorTest with throwOnError:
true can leave React Query's error state uncleared; wrap the component (or the
part rendering the query) with a QueryErrorResetBoundary so errors are reset
correctly on retry or key changes instead of relying on enabled
toggles—specifically, update the component that renders ApiErrorTest (or
ApiErrorBoundary if present) to include QueryErrorResetBoundary around the
useQuery consumer and ensure resetKeys include ['api-error-test', errorType] so
useQuery (with throwOnError) is reset properly; keep the existing queryKey and
enabled logic but add the boundary to guarantee cache error reset.
- Around line 282-284: Replace the plain location assignment in the
Styled.BackButton onClick with React Router navigation: import and call the
useNavigate hook (from 'react-router-dom') inside the ErrorTestPage component to
get a navigate function, then change the onClick handler on Styled.BackButton to
call navigate('/') so navigation stays within the SPA; ensure the import and the
navigate const are defined in the same component scope where Styled.BackButton
is used.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0be6976 and 5740afe.

📒 Files selected for processing (2)
  • frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx
  • frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx

@seongwon030 seongwon030 linked an issue Feb 25, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] MOA-660 계층적 에러바운더리 설계

2 participants