Skip to content

[feat] 404 50x 에러페이지 구현#155

Merged
ff1451 merged 10 commits intodevelopfrom
154-feat-404-50x-에러페이지-구현
Mar 3, 2026

Hidden character warning

The head ref may contain hidden characters: "154-feat-404-50x-\uc5d0\ub7ec\ud398\uc774\uc9c0-\uad6c\ud604"
Merged

[feat] 404 50x 에러페이지 구현#155
ff1451 merged 10 commits intodevelopfrom
154-feat-404-50x-에러페이지-구현

Conversation

@ff1451
Copy link
Collaborator

@ff1451 ff1451 commented Mar 3, 2026

Summary by CodeRabbit

  • 새로운 기능

    • 404 및 500 오류 페이지와 공통 에러 레이아웃 추가
    • 에러 페이지에서 홈으로 이동하는 훅 추가
    • 메인 라우터에 서버 오류 경로 및 캐치올 경로 추가
  • 버그 수정

    • 5xx 응답 자동 감지 후 서버 오류 페이지로 안전 리디렉션 개선
    • 네트워크/타임아웃 오류 처리 표준화 및 관련 재시도 로직 보완(상태 0 재시도 허용)
    • 서버오류 경로에서 초기화 및 로딩 처리를 건너뛰도록 변경, 로딩 접근성 개선

@ff1451 ff1451 linked an issue Mar 3, 2026 that may be closed by this pull request
@ff1451 ff1451 self-assigned this Mar 3, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Warning

Rate limit exceeded

@ff1451 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 7630943 and 6ab06f6.

📒 Files selected for processing (2)
  • src/apis/auth/index.ts
  • src/apis/client.ts

Walkthrough

404 및 5xx 전용 페이지와 공통 레이아웃 추가, 서버-에러 감지/리다이렉트 유틸 도입, API 클라이언트의 네트워크·서버 에러 표준화, Auth 초기화 스킵 로직 및 react-query 재시도 정책 도입으로 전반적인 에러 처리 흐름을 확장했습니다.

Changes

Cohort / File(s) Summary
에러 페이지 컴포넌트
src/pages/NotFound/index.tsx, src/pages/ServerError/index.tsx, src/components/common/ErrorPageLayout.tsx
NotFound 및 ServerError 페이지 추가, 재사용 가능한 ErrorPageLayout 컴포넌트 도입과 홈 네비게이션 연결.
라우팅
src/App.tsx
라우트에 /server-error 경로 추가 및 catch-all(*)으로 NotFoundPage 연결.
API 클라이언트 공통 에러 처리
src/apis/client.ts
네트워크/Abort/타임아웃 감지 및 ApiError 생성/재처리 유틸 추가, 5xx 응답 시 서버 에러 리다이렉트 호출로 에러 처리 중앙화.
인증 API 변경
src/apis/auth/index.ts
refreshAccessToken에 네트워크 예외 래핑, 5xx일 경우 서버 에러 페이지 리다이렉트 및 ApiError 표준화 적용.
에러 리다이렉트 유틸
src/utils/ts/errorRedirect.ts
SERVER_ERROR_PATH, isServerErrorStatus, redirectToServerErrorPage 추가(브라우저 실행 체크 및 중복 경로 방지 포함).
Auth 초기화 / 로딩 처리
src/components/auth/AuthGuard.tsx
현재 경로가 서버 에러 페이지면 initialize 호출 및 로딩 스피너 렌더링을 건너뛰도록 조건 추가(로딩 컨테이너에 role/status 및 sr-only 텍스트 추가).
전역 설정 (react-query)
src/main.tsx
react-query 기본 retry를 고정 false에서, ApiError(status === 0)인 네트워크 에러에 한해 최대 2회 재시도로 변경.
홈 네비게이션 훅
src/utils/hooks/useErrorPageHomeNavigation.ts
에러 페이지의 홈 버튼 클릭 시 인증 상태에 따라 /home 또는 /으로 replace 이동하는 훅 추가.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경사항의 주요 내용을 명확히 나타냅니다. 404 및 50x 에러 페이지 구현이 핵심 기능이며, 제목이 이를 정확히 반영합니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 154-feat-404-50x-에러페이지-구현

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.

@ff1451 ff1451 changed the title 154 feat 404 50x 에러페이지 구현 [feat] 404 50x 에러페이지 구현 Mar 3, 2026
Copy link

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/auth/AuthGuard.tsx (1)

20-24: ⚠️ Potential issue | 🟡 Minor

로딩 스피너 접근성 속성을 추가해 주세요.

Line 20~24의 로딩 UI는 시각적으로만 표현되어 스크린리더가 상태 변화를 인지하기 어렵습니다. role="status"와 보조 텍스트를 넣어 주세요.

♿ 제안 코드
-      <div className="flex h-screen items-center justify-center">
-        <div className="h-8 w-8 animate-spin rounded-full border-4 border-indigo-200 border-t-indigo-600" />
+      <div className="flex h-screen items-center justify-center" role="status" aria-live="polite">
+        <span className="sr-only">로딩 중</span>
+        <div
+          aria-hidden="true"
+          className="h-8 w-8 animate-spin rounded-full border-4 border-indigo-200 border-t-indigo-600"
+        />
       </div>

As per coding guidelines, src/components/**: 접근성(aria-*, role, 키보드 탐색)이 적절히 처리되는지.

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

In `@src/components/auth/AuthGuard.tsx` around lines 20 - 24, The loading UI
returned from the AuthGuard component when isLoading && !shouldSkipInitialize is
true is currently only visual; update that JSX to be accessible by adding
role="status" on the container (the div wrapping the spinner) and include an
offscreen/visually-hidden assistive text (e.g., a <span> with a "sr-only" class
or aria-live text) such as "Loading…" so screen readers announce the state;
locate the conditional return in AuthGuard (the block that renders the div with
className="flex h-screen items-center justify-center" and the spinner element
with className including "animate-spin") and add the role and hidden text
accordingly.
🧹 Nitpick comments (5)
src/utils/ts/errorRedirect.ts (1)

1-1: 경로 상수를 export 해서 전역 재사용해 주세요.

Line 1의 SERVER_ERROR_PATH를 export하면 라우트/가드/리다이렉트 간 문자열 중복을 없앨 수 있습니다.

♻️ 제안 코드
-const SERVER_ERROR_PATH = '/server-error';
+export const SERVER_ERROR_PATH = '/server-error';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ts/errorRedirect.ts` at line 1, Export the SERVER_ERROR_PATH
constant so it can be reused globally: change the declaration in
errorRedirect.ts to an exported constant (e.g., export const SERVER_ERROR_PATH =
'/server-error') and then update any places that use the literal string (routes,
guards, redirects) to import SERVER_ERROR_PATH from this module to remove
duplicated string literals and centralize the path.
src/pages/NotFound/index.tsx (1)

5-37: 에러 페이지 UI를 공통 컴포넌트로 추출해 주세요.

src/pages/ServerError/index.tsx와 레이아웃/버튼 스타일이 거의 동일합니다. 공통 ErrorPageLayout로 추출하면 유지보수가 쉬워집니다.

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

In `@src/pages/NotFound/index.tsx` around lines 5 - 37, Create a reusable
ErrorPageLayout component and replace duplicate UI in NotFoundPage and
ServerError pages: extract the shared JSX (container section, image, title,
message block, and primary button) from NotFoundPage into a new ErrorPageLayout
component that accepts props like imageSrc (NotFoundCatImage), title ("오류가
발생했어요"), message (multi-line description), primaryLabel ("홈으로 가기"), and
onPrimaryClick (handleGoHome / server error handler); then update NotFoundPage
(function NotFoundPage) and ServerError page to import and render
<ErrorPageLayout .../> passing the appropriate props and move navigation logic
(useNavigate + useAuthStore) into the pages so ErrorPageLayout is
presentation-only.
src/apis/client.ts (1)

152-175: 오류 처리 분기를 헬퍼로 추출해 중복을 줄여 주세요.

sendRequestsendRequestWithoutRetry의 서버에러 리다이렉트/네트워크 에러 매핑 로직이 동일하게 반복됩니다. 공통 함수로 묶어두면 정책 변경 시 누락 위험이 줄어듭니다.

Also applies to: 268-291

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

In `@src/apis/client.ts` around lines 152 - 175, Extract the duplicated
error-handling branches from sendRequest and sendRequestWithoutRetry into a
single helper (e.g., handleApiFetchError or mapFetchError) that accepts the
Response (or thrown error) plus the request url, and performs: 1) the
server-error check via isServerErrorStatus and calls
redirectToServerErrorPage(); 2) parsing the response with parseErrorResponse and
constructing/throwing the ApiError (setting status, statusText, url, apiError)
when response.ok is false; and 3) mapping network errors using
isFetchNetworkError and createNetworkApiError (and handling AbortError). Replace
the duplicated blocks in sendRequest and sendRequestWithoutRetry to call this
helper (use parseResponse only on success).
src/components/auth/AuthGuard.tsx (1)

12-12: 서버 에러 경로 문자열을 상수로 통일해 주세요.

Line 12의 '/server-error' 하드코딩은 라우트/리다이렉트 경로와 드리프트가 생기기 쉽습니다. errorRedirect.ts의 경로 상수를 재사용하는 쪽이 안전합니다.

♻️ 제안 코드
+import { SERVER_ERROR_PATH } from '@/utils/ts/errorRedirect';
...
-  const shouldSkipInitialize = pathname === '/server-error';
+  const shouldSkipInitialize = pathname === SERVER_ERROR_PATH;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/auth/AuthGuard.tsx` at line 12, Replace the hardcoded
'/server-error' string in the shouldSkipInitialize assignment with the shared
route constant exported from errorRedirect.ts (import the exported constant and
use it instead of the literal), update the AuthGuard.tsx import list to bring in
that constant, and ensure shouldSkipInitialize = pathname ===
<ERROR_ROUTE_CONSTANT> so the component reuses the canonical server-error route
definition.
src/apis/auth/index.ts (1)

14-20: 토큰 갱신 실패 에러도 표준 API 에러 형태로 맞춰 주세요.

Line 19에서 일반 Error를 던지면, 다른 API 레이어와 달리 상태코드 기반 처리 일관성이 깨집니다. ApiError 형태로 매핑해 전달하는 편이 안전합니다.

♻️ 제안 코드
+import type { ApiError } from '@/interface/error';
...
-    throw new Error('토큰 갱신 실패');
+    const error = new Error('토큰 갱신 실패') as ApiError;
+    error.status = response.status;
+    error.statusText = response.statusText;
+    error.url = response.url;
+    throw error;

As per coding guidelines, src/apis/**: 에러가 UI 친화적인 형태로 매핑/전달되는지공통 API 클라이언트 설정과 일관된지.

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

In `@src/apis/auth/index.ts` around lines 14 - 20, Replace the plain Error thrown
on token refresh failure with the standard ApiError so the API layer remains
consistent: inside the block that checks if (!response.ok) (the same scope using
isServerErrorStatus(response.status) and redirectToServerErrorPage()), construct
and throw an ApiError instance that includes the HTTP status code and a
UI-friendly message (e.g., new ApiError(response.status, '토큰 갱신 실패')) and
preserve any response body/error details if available so other layers can handle
it uniformly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/auth/AuthGuard.tsx`:
- Around line 20-24: The loading UI returned from the AuthGuard component when
isLoading && !shouldSkipInitialize is true is currently only visual; update that
JSX to be accessible by adding role="status" on the container (the div wrapping
the spinner) and include an offscreen/visually-hidden assistive text (e.g., a
<span> with a "sr-only" class or aria-live text) such as "Loading…" so screen
readers announce the state; locate the conditional return in AuthGuard (the
block that renders the div with className="flex h-screen items-center
justify-center" and the spinner element with className including "animate-spin")
and add the role and hidden text accordingly.

---

Nitpick comments:
In `@src/apis/auth/index.ts`:
- Around line 14-20: Replace the plain Error thrown on token refresh failure
with the standard ApiError so the API layer remains consistent: inside the block
that checks if (!response.ok) (the same scope using
isServerErrorStatus(response.status) and redirectToServerErrorPage()), construct
and throw an ApiError instance that includes the HTTP status code and a
UI-friendly message (e.g., new ApiError(response.status, '토큰 갱신 실패')) and
preserve any response body/error details if available so other layers can handle
it uniformly.

In `@src/apis/client.ts`:
- Around line 152-175: Extract the duplicated error-handling branches from
sendRequest and sendRequestWithoutRetry into a single helper (e.g.,
handleApiFetchError or mapFetchError) that accepts the Response (or thrown
error) plus the request url, and performs: 1) the server-error check via
isServerErrorStatus and calls redirectToServerErrorPage(); 2) parsing the
response with parseErrorResponse and constructing/throwing the ApiError (setting
status, statusText, url, apiError) when response.ok is false; and 3) mapping
network errors using isFetchNetworkError and createNetworkApiError (and handling
AbortError). Replace the duplicated blocks in sendRequest and
sendRequestWithoutRetry to call this helper (use parseResponse only on success).

In `@src/components/auth/AuthGuard.tsx`:
- Line 12: Replace the hardcoded '/server-error' string in the
shouldSkipInitialize assignment with the shared route constant exported from
errorRedirect.ts (import the exported constant and use it instead of the
literal), update the AuthGuard.tsx import list to bring in that constant, and
ensure shouldSkipInitialize = pathname === <ERROR_ROUTE_CONSTANT> so the
component reuses the canonical server-error route definition.

In `@src/pages/NotFound/index.tsx`:
- Around line 5-37: Create a reusable ErrorPageLayout component and replace
duplicate UI in NotFoundPage and ServerError pages: extract the shared JSX
(container section, image, title, message block, and primary button) from
NotFoundPage into a new ErrorPageLayout component that accepts props like
imageSrc (NotFoundCatImage), title ("오류가 발생했어요"), message (multi-line
description), primaryLabel ("홈으로 가기"), and onPrimaryClick (handleGoHome / server
error handler); then update NotFoundPage (function NotFoundPage) and ServerError
page to import and render <ErrorPageLayout .../> passing the appropriate props
and move navigation logic (useNavigate + useAuthStore) into the pages so
ErrorPageLayout is presentation-only.

In `@src/utils/ts/errorRedirect.ts`:
- Line 1: Export the SERVER_ERROR_PATH constant so it can be reused globally:
change the declaration in errorRedirect.ts to an exported constant (e.g., export
const SERVER_ERROR_PATH = '/server-error') and then update any places that use
the literal string (routes, guards, redirects) to import SERVER_ERROR_PATH from
this module to remove duplicated string literals and centralize the path.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bab158 and 2afa81d.

⛔ Files ignored due to path filters (1)
  • src/assets/image/not-found-cat.webp is excluded by !src/assets/** and included by **
📒 Files selected for processing (8)
  • src/App.tsx
  • src/apis/auth/index.ts
  • src/apis/client.ts
  • src/components/auth/AuthGuard.tsx
  • src/main.tsx
  • src/pages/NotFound/index.tsx
  • src/pages/ServerError/index.tsx
  • src/utils/ts/errorRedirect.ts

Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/apis/auth/index.ts (1)

8-24: ⚠️ Potential issue | 🟠 Major

refreshAccessToken는 네트워크 오류 처리가 부재하고 공통 API 패턴과 일관되지 않습니다.

fetch 네트워크 실패 시 try/catch로 잡지 않아 ApiError 표준화가 누락됩니다. 같은 파일의 signup, getMyInfo, logout처럼 apiClient.post를 사용하거나, 불가피하게 직접 fetch를 쓸 경우 apiClienttry/catch + rethrowFetchError 패턴을 따라 네트워크 오류를 일관되게 처리해야 합니다.

수정 예시
-export const refreshAccessToken = async (): Promise<string> => {
-  const url = `${BASE_URL.replace(/\/+$/, '')}/users/refresh`;
-  const response = await fetch(url, {
-    method: 'POST',
-    credentials: 'include',
-  });
+export const refreshAccessToken = async (): Promise<string> => {
+  const response = await apiClient.post<RefreshTokenResponse>('users/refresh', {
+    requiresAuth: false,
+  });
-  if (!response.ok) {
-    if (isServerErrorStatus(response.status)) {
-      redirectToServerErrorPage();
-    }
-
-    const error = new Error('토큰 갱신 실패') as ApiError;
-    error.status = response.status;
-    error.statusText = response.statusText;
-    error.url = url;
-    throw error;
-  }
-
-  const data: RefreshTokenResponse = await response.json();
-  return data.accessToken;
+  return response.accessToken;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apis/auth/index.ts` around lines 8 - 24, refreshAccessToken currently
calls fetch directly without handling network errors or following the module's
ApiError normalization; either replace the direct fetch with the shared
apiClient.post call used by signup/getMyInfo/logout or wrap the existing fetch
in a try/catch and call the shared rethrowFetchError logic so network failures
are converted into ApiError consistently: update refreshAccessToken to use
apiClient.post('/users/refresh', { credentials: 'include' }) or surround the
fetch(...) call with try { ... } catch (err) { rethrowFetchError(err,
'refreshAccessToken', url) } and ensure any thrown ApiError has status,
statusText and url set as done elsewhere.
🧹 Nitpick comments (2)
src/apis/client.ts (1)

59-62: API 레이어에서 즉시 리다이렉트는 분리 권장합니다.

throwApiError에서 네비게이션을 직접 수행하면 API 계층에 UI 책임이 섞입니다. 여기서는 표준화된 에러만 던지고, 이동은 상위(라우터/에러 바운더리)에서 status 기반으로 처리하는 편이 안전합니다.

♻️ 제안 diff
 async function throwApiError(response: Response): Promise<never> {
-  if (isServerErrorStatus(response.status)) {
-    redirectToServerErrorPage();
-  }
-
   const errorData = await parseErrorResponse(response);

As per coding guidelines src/apis/**: 비즈니스 로직이나 React 훅 의존성이 섞이지 않았는지.

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

In `@src/apis/client.ts` around lines 59 - 62, The function throwApiError
currently performs UI navigation (calls redirectToServerErrorPage) which mixes
API layer with UI; change throwApiError to only create and throw a standardized
error object (include response.status and any parsed body/details) and remove
the redirectToServerErrorPage call from it, and let higher-level code
(router/error boundary or the caller) inspect
isServerErrorStatus(response.status) and perform navigation or handling; update
any callers of throwApiError to catch the thrown error and perform redirect when
appropriate.
src/pages/ServerError/index.tsx (1)

6-12: 홈 이동 비즈니스 로직은 커스텀 훅으로 분리해 주세요.

useAuthStore 조회 + 분기 네비게이션 로직이 페이지에 직접 있어, NotFound 페이지와 함께 useErrorPageHomeNavigation 같은 훅으로 묶는 편이 컨벤션/중복 측면에서 더 좋습니다.

As per coding guidelines src/pages/**/index.tsx: 비즈니스 로직은 커스텀 훅으로 분리되어 있는지.

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

In `@src/pages/ServerError/index.tsx` around lines 6 - 12, Extract the
home-navigation business logic from ServerErrorPage into a custom hook (e.g.,
useErrorPageHomeNavigation) that encapsulates useAuthStore and useNavigate;
replace the inline handleGoHome and the direct useAuthStore call in
ServerErrorPage with a hook call that returns a goHome function (or navigate
handler) and any needed state, so NotFound and other error pages can reuse the
same logic and ServerErrorPage only handles rendering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/auth/index.ts`:
- Around line 16-24: The server-error redirect call redirectToServerErrorPage()
currently returns void so execution continues and the ApiError is still
constructed and thrown; to fix, either make redirectToServerErrorPage() have a
never return type (so TypeScript and readers know it never returns) or, more
simply, add an explicit early return immediately after the
redirectToServerErrorPage() call in the block where
isServerErrorStatus(response.status) is true to prevent the subsequent
construction/throw of the ApiError (adjust code in the function containing
isServerErrorStatus check and the error/throw logic).

In `@src/apis/client.ts`:
- Around line 75-92: rethrowFetchError currently throws a plain Error for
AbortError which breaks the ApiError contract; update the AbortError branch to
construct and throw an ApiError (same shape as createNetworkApiError) with a
localized timeout message, set error.name (e.g., 'TimeoutError'), status (e.g.,
0), statusText (e.g., 'TIMEOUT'), and url (pass through the url param) so the UI
receives the same standardized error shape; you can reuse the pattern in
createNetworkApiError to build the ApiError for the timeout case inside
rethrowFetchError.

In `@src/components/auth/AuthGuard.tsx`:
- Around line 23-24: In AuthGuard.tsx update the screen-reader-only loading text
to use localization instead of the fixed English string: replace the hardcoded
<span className="sr-only">Loading…</span> with a localized message (e.g., use
your i18n helper like t('loading') or a localized constant) so the sr-only
content reflects the user's locale and matches the app's
accessibility/localization standards.

---

Outside diff comments:
In `@src/apis/auth/index.ts`:
- Around line 8-24: refreshAccessToken currently calls fetch directly without
handling network errors or following the module's ApiError normalization; either
replace the direct fetch with the shared apiClient.post call used by
signup/getMyInfo/logout or wrap the existing fetch in a try/catch and call the
shared rethrowFetchError logic so network failures are converted into ApiError
consistently: update refreshAccessToken to use apiClient.post('/users/refresh',
{ credentials: 'include' }) or surround the fetch(...) call with try { ... }
catch (err) { rethrowFetchError(err, 'refreshAccessToken', url) } and ensure any
thrown ApiError has status, statusText and url set as done elsewhere.

---

Nitpick comments:
In `@src/apis/client.ts`:
- Around line 59-62: The function throwApiError currently performs UI navigation
(calls redirectToServerErrorPage) which mixes API layer with UI; change
throwApiError to only create and throw a standardized error object (include
response.status and any parsed body/details) and remove the
redirectToServerErrorPage call from it, and let higher-level code (router/error
boundary or the caller) inspect isServerErrorStatus(response.status) and perform
navigation or handling; update any callers of throwApiError to catch the thrown
error and perform redirect when appropriate.

In `@src/pages/ServerError/index.tsx`:
- Around line 6-12: Extract the home-navigation business logic from
ServerErrorPage into a custom hook (e.g., useErrorPageHomeNavigation) that
encapsulates useAuthStore and useNavigate; replace the inline handleGoHome and
the direct useAuthStore call in ServerErrorPage with a hook call that returns a
goHome function (or navigate handler) and any needed state, so NotFound and
other error pages can reuse the same logic and ServerErrorPage only handles
rendering.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2afa81d and 9ee1e84.

📒 Files selected for processing (7)
  • src/apis/auth/index.ts
  • src/apis/client.ts
  • src/components/auth/AuthGuard.tsx
  • src/components/common/ErrorPageLayout.tsx
  • src/pages/NotFound/index.tsx
  • src/pages/ServerError/index.tsx
  • src/utils/ts/errorRedirect.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/ts/errorRedirect.ts

Comment on lines +23 to +24
<div role="status" className="flex h-screen items-center justify-center">
<span className="sr-only">Loading…</span>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

로딩 보조 텍스트 로컬라이징 권장

스크린리더 전용 문구(Loading…)를 한국어로 맞추면 접근성/로케일 일관성이 더 좋아집니다.

As per coding guidelines src/components/**: 접근성(aria-*, role, 키보드 탐색)이 적절히 처리되는지.

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

In `@src/components/auth/AuthGuard.tsx` around lines 23 - 24, In AuthGuard.tsx
update the screen-reader-only loading text to use localization instead of the
fixed English string: replace the hardcoded <span
className="sr-only">Loading…</span> with a localized message (e.g., use your
i18n helper like t('loading') or a localized constant) so the sr-only content
reflects the user's locale and matches the app's accessibility/localization
standards.

Copy link

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/auth/index.ts`:
- Around line 30-33: The current branch in isServerErrorStatus handling calls
redirectToServerErrorPage() but then does "return undefined as never", which
still resolves and lets callers receive undefined; replace that return with an
explicit throw (e.g., throw new Error("Redirecting to server error page") or a
dedicated RedirectError) so execution stops immediately after calling
redirectToServerErrorPage(); update any surrounding try/catch if needed in
callers of the function containing isServerErrorStatus to handle the thrown
error.

In `@src/apis/client.ts`:
- Around line 75-83: The AbortError is being unconditionally mapped to a
TimeoutError in rethrowFetchError; change rethrowFetchError(error: unknown, url:
string) to accept an additional optional boolean flag (e.g., isTimeout = false)
and only create/throw a TimeoutError when error is an AbortError AND isTimeout
is true; otherwise map AbortError to a generic cancel/abort ApiError (e.g., name
'Canceled' or preserve original) so caller-cancellations aren’t mislabeled as
timeouts. Update all callers (including the other occurrences you noted around
lines 189 and 287) to pass true when the abort originates from a timeout path
and false (or omit) when the abort comes from a caller-provided signal.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ee1e84 and a6ff349.

📒 Files selected for processing (6)
  • src/apis/auth/index.ts
  • src/apis/client.ts
  • src/components/auth/AuthGuard.tsx
  • src/pages/NotFound/index.tsx
  • src/pages/ServerError/index.tsx
  • src/utils/hooks/useErrorPageHomeNavigation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/ServerError/index.tsx
  • src/pages/NotFound/index.tsx

Copy link

@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 (2)
src/apis/client.ts (1)

59-73: 서버 에러 시 불필요한 parseErrorResponse 호출.

isServerErrorStatus 분기에서 리다이렉트 후에도 parseErrorResponse가 실행됩니다. 서버 에러 페이지로 이동할 예정이므로 에러 파싱 없이 즉시 throw하는 것이 효율적입니다.

♻️ 제안 diff
 async function throwApiError(response: Response): Promise<never> {
   if (isServerErrorStatus(response.status)) {
     redirectToServerErrorPage();
+    throw new Error('서버 오류가 발생했습니다.');
   }

   const errorData = await parseErrorResponse(response);

   const error = new Error(errorData?.message ?? 'API 요청 실패') as ApiError;
+  error.name = 'ApiError';
   error.status = response.status;
   error.statusText = response.statusText;
   error.url = response.url;
   error.apiError = errorData ?? undefined;

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

In `@src/apis/client.ts` around lines 59 - 73, The server-error branch in
throwApiError currently calls redirectToServerErrorPage() but still continues to
parse the response; change throwApiError so that if
isServerErrorStatus(response.status) is true you call
redirectToServerErrorPage() and then immediately stop execution (throw/return)
without calling parseErrorResponse; move the parseErrorResponse and construction
of the ApiError below that branch so parseErrorResponse is only invoked for
non-server errors, and reference the throwApiError, isServerErrorStatus,
parseErrorResponse, and redirectToServerErrorPage symbols when making the
change.
src/apis/auth/index.ts (1)

35-39: error.name 속성 누락으로 에러 타입 식별 불가.

네트워크 에러(Line 20)에서는 name = 'NetworkError'를 설정하지만, 토큰 갱신 실패 에러는 name이 없어 에러 핸들러에서 타입 구분이 어렵습니다.

♻️ 제안 diff
     const error = new Error('토큰 갱신 실패') as ApiError;
+    error.name = 'AuthError';
     error.status = response.status;
     error.statusText = response.statusText;
     error.url = url;
     throw error;

As per coding guidelines src/apis/**: 에러가 UI 친화적인 형태로 매핑/전달되는지.

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

In `@src/apis/auth/index.ts` around lines 35 - 39, The token-refresh error
creation sets ApiError fields but omits error.name, making runtime handlers
unable to distinguish types; update the block that constructs the ApiError (the
const error = new Error('토큰 갱신 실패') as ApiError) to set error.name to a
consistent value (e.g., 'TokenRefreshError')—matching the pattern used for
network errors (name = 'NetworkError')—so downstream error handlers can branch
on error.name along with status/statusText/url.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/apis/auth/index.ts`:
- Around line 35-39: The token-refresh error creation sets ApiError fields but
omits error.name, making runtime handlers unable to distinguish types; update
the block that constructs the ApiError (the const error = new Error('토큰 갱신 실패')
as ApiError) to set error.name to a consistent value (e.g.,
'TokenRefreshError')—matching the pattern used for network errors (name =
'NetworkError')—so downstream error handlers can branch on error.name along with
status/statusText/url.

In `@src/apis/client.ts`:
- Around line 59-73: The server-error branch in throwApiError currently calls
redirectToServerErrorPage() but still continues to parse the response; change
throwApiError so that if isServerErrorStatus(response.status) is true you call
redirectToServerErrorPage() and then immediately stop execution (throw/return)
without calling parseErrorResponse; move the parseErrorResponse and construction
of the ApiError below that branch so parseErrorResponse is only invoked for
non-server errors, and reference the throwApiError, isServerErrorStatus,
parseErrorResponse, and redirectToServerErrorPage symbols when making the
change.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6ff349 and 7630943.

📒 Files selected for processing (2)
  • src/apis/auth/index.ts
  • src/apis/client.ts

@ff1451 ff1451 merged commit 8b25aea into develop Mar 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 404, 50x 에러페이지 구현

1 participant