[Feature] 계층적 에러 핸들 (Global/Content/Api ErrorBoundary) 도입#1214
[Feature] 계층적 에러 핸들 (Global/Content/Api ErrorBoundary) 도입#1214seongwon030 merged 18 commits intodevelop-fefrom
Conversation
- 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 활성화 제어 기능 추가
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| 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
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
- [Feature] Promotion API 추가 및 테스트 인프라 구축 #1136:
frontend/src/apis/utils/apiHelpers.ts변경과 직접적 충돌 가능성(기존 처리 로직 관련). - [release] FE v1.1.24 #1203: ErrorBoundary 확장 및 App 통합 관련 연속 작업으로 코드 레벨 유사성 높음.
- [Feature] 전역 에러 처리 및 Sentry 연동 #1196: ErrorBoundary 도입/리팩터링 관련 이전 PR과 직접 연관.
Suggested reviewers
- lepitaaar
- oesnuj
- suhyun113
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
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에 이미 정의되어 있습니다. 예를 들어#ff5414는colors.primary[900],#787878은colors.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:errornull 체크가 불필요합니다.
ErrorFallbackProps에서error는Error타입으로 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:WarningIcon과ApiErrorFallback의AlertIcon이 거의 동일한 패턴입니다.두 폴백 컴포넌트 모두 인라인 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:errornull 체크가 불필요합니다.
ContentErrorFallback과 동일하게,ErrorFallbackProps에서error는 non-nullableError타입이므로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가 마지막 파라미터인데,errorCode와data없이message만 전달하려면 중간 인자에undefined/null을 채워야 합니다. 실제로ErrorTestPage.tsx에서new ApiError(404, 'Not Found', 'RESOURCE_NOT_FOUND', null, '...')처럼data에null을 명시적으로 전달하고 있습니다.옵션 객체 패턴을 사용하면 호출부가 더 명확해집니다:
옵션 객체 패턴 예시
+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: 명명 일관성:GlobalBoundaryvsContentErrorBoundary/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)이 잘 정의되어 있으나, 이 파일은 직접 색상값을 하드코딩하고 있습니다. 다음과 같이 매핑하여 교체해주세요:
#989898→theme.colors.gray[600]#333333→theme.colors.gray[900]#ebebeb→theme.colors.gray[300]#f8f8f8→theme.colors.gray[100]#ff5414→theme.colors.primary[900]#dcdcdc→theme.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.
frontend/src/components/common/ErrorBoundary/ApiError/ApiErrorFallback.styles.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
🧹 Nitpick comments (1)
frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsx (1)
4-4: 불필요하게 우회하는 임포트 경로를 단순화하세요.
ContentErrorBoundary.tsx는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 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 기반으로 명시하여 환경 구분 강
lepitaaar
left a comment
There was a problem hiding this comment.
에러 바운더리 계층으로 나눈거 수고하셨습니다 혹시나 디버깅하기좋겠는데요
There was a problem hiding this comment.
♻️ Duplicate comments (1)
frontend/src/pages/ErrorTestPage/ErrorTestPage.tsx (1)
194-196: 이전 리뷰에서 지적된resetKeys누락 이슈가 올바르게 해결되었습니다
ContentErrorBoundary에resetKeys={[contentError]}전달 →contentError상태 변경 시 바운더리 자동 리셋ApiErrorBoundary에resetKeys={[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 캐시의 에러 상태가 유지될 수 있습니다. 이 테스트 페이지의 경우queryKey에errorType이 포함되어 있고 리셋 시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.
📒 Files selected for processing (2)
frontend/src/components/common/ErrorBoundary/ContentError/ContentErrorBoundary.tsxfrontend/src/pages/ErrorTestPage/ErrorTestPage.tsx
#️⃣연관된 이슈
📝작업 내용
전역 에러바운더리 하나로는 어떤 부분에서 에러가 났는지 구체적으로 알기 어려웠습니다. 그래서 Api에러 -> 페이지(or컴포넌트)에러
-> 전역에러 이렇게 3개의 계층으로 나누어, 최상위로 에러가 전파되도록 설계했습니다.
1. 에러 바운더리 계층화
BaseErrorBoundary: 공통 로직(에러 캐치, 리셋, 상태 관리)을 담당하는 클래스 컴포넌트 구현.GlobalErrorBoundary (Level 1):ContentErrorBoundary (Level 2):ApiErrorBoundary (Level 3):2. 커스텀 에러 클래스 정의
3. 유틸리티 및 설정 개선
VITE_ENABLE_SENTRY_IN_DEV=true로 활성화할 수 있도록 설정 개선.중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
새로운 기능
버그 수정