+
);
diff --git a/src/components/common/ErrorPageLayout.tsx b/src/components/common/ErrorPageLayout.tsx
new file mode 100644
index 00000000..c50a8b9d
--- /dev/null
+++ b/src/components/common/ErrorPageLayout.tsx
@@ -0,0 +1,37 @@
+import type { ReactNode } from 'react';
+
+interface ErrorPageLayoutProps {
+ imageSrc: string;
+ imageAlt: string;
+ title: string;
+ message: ReactNode;
+ primaryLabel: string;
+ onPrimaryClick: () => void;
+}
+
+function ErrorPageLayout({ imageSrc, imageAlt, title, message, primaryLabel, onPrimaryClick }: ErrorPageLayoutProps) {
+ return (
+
+
+

+
+
+
+
+
+
+
+
+ );
+}
+
+export default ErrorPageLayout;
diff --git a/src/main.tsx b/src/main.tsx
index 884e6b70..e6dcd96b 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -5,6 +5,7 @@ import { createRoot } from 'react-dom/client';
import './index.css';
import { initSentry } from './config/sentry.ts';
import ToastProvider from './contexts/ToastContext';
+import { isApiError } from './interface/error.ts';
import { installViewportVars } from './utils/ts/viewport.ts';
installViewportVars();
@@ -14,7 +15,10 @@ const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnReconnect: true,
- retry: false,
+ retry: (failureCount, error) => {
+ const maxRetries = 2;
+ return failureCount <= maxRetries && isApiError(error) && error.status === 0;
+ },
},
},
});
diff --git a/src/pages/NotFound/index.tsx b/src/pages/NotFound/index.tsx
new file mode 100644
index 00000000..b7b65adf
--- /dev/null
+++ b/src/pages/NotFound/index.tsx
@@ -0,0 +1,26 @@
+import NotFoundCatImage from '@/assets/image/not-found-cat.webp';
+import ErrorPageLayout from '@/components/common/ErrorPageLayout';
+import { useErrorPageHomeNavigation } from '@/utils/hooks/useErrorPageHomeNavigation';
+
+function NotFoundPage() {
+ const handleGoHome = useErrorPageHomeNavigation();
+
+ return (
+
+ 주소가 잘못 입력되었거나
+
+ 삭제되어 페이지를 찾을 수 없어요
+ >
+ }
+ primaryLabel="홈으로 가기"
+ onPrimaryClick={handleGoHome}
+ />
+ );
+}
+
+export default NotFoundPage;
diff --git a/src/pages/ServerError/index.tsx b/src/pages/ServerError/index.tsx
new file mode 100644
index 00000000..68f6a7f1
--- /dev/null
+++ b/src/pages/ServerError/index.tsx
@@ -0,0 +1,26 @@
+import NotFoundCatImage from '@/assets/image/not-found-cat.webp';
+import ErrorPageLayout from '@/components/common/ErrorPageLayout';
+import { useErrorPageHomeNavigation } from '@/utils/hooks/useErrorPageHomeNavigation';
+
+function ServerErrorPage() {
+ const handleGoHome = useErrorPageHomeNavigation();
+
+ return (
+
+ 서버에 일시적인 문제가 생겼어요
+
+ 잠시 후 다시 시도해 주세요
+ >
+ }
+ primaryLabel="홈으로 가기"
+ onPrimaryClick={handleGoHome}
+ />
+ );
+}
+
+export default ServerErrorPage;
diff --git a/src/utils/hooks/useErrorPageHomeNavigation.ts b/src/utils/hooks/useErrorPageHomeNavigation.ts
new file mode 100644
index 00000000..5a392acf
--- /dev/null
+++ b/src/utils/hooks/useErrorPageHomeNavigation.ts
@@ -0,0 +1,11 @@
+import { useNavigate } from 'react-router-dom';
+import { useAuthStore } from '@/stores/authStore';
+
+export function useErrorPageHomeNavigation() {
+ const navigate = useNavigate();
+ const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
+
+ return () => {
+ navigate(isAuthenticated ? '/home' : '/', { replace: true });
+ };
+}
diff --git a/src/utils/ts/errorRedirect.ts b/src/utils/ts/errorRedirect.ts
new file mode 100644
index 00000000..ec3acb98
--- /dev/null
+++ b/src/utils/ts/errorRedirect.ts
@@ -0,0 +1,12 @@
+export const SERVER_ERROR_PATH = '/server-error';
+
+export function isServerErrorStatus(status: number): boolean {
+ return status >= 500 && status < 600;
+}
+
+export function redirectToServerErrorPage(): void {
+ if (typeof window === 'undefined') return;
+ if (window.location.pathname === SERVER_ERROR_PATH) return;
+
+ window.location.replace(SERVER_ERROR_PATH);
+}