diff --git a/src/components/layout/RouteTransition.module.css b/src/components/layout/RouteTransition.module.css
index 0000021..7ac3e74 100644
--- a/src/components/layout/RouteTransition.module.css
+++ b/src/components/layout/RouteTransition.module.css
@@ -2,6 +2,24 @@
animation: fadeIn var(--fowoco-motion-base) var(--fowoco-easing-decelerate);
}
+.loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--fowoco-spacing-8);
+ min-height: 240px;
+ color: var(--text-secondary);
+ font-size: 13px;
+}
+
+.loadingDot {
+ width: 8px;
+ height: 8px;
+ border-radius: var(--fowoco-radius-999);
+ background: var(--brand-primary);
+ animation: loadingPulse 900ms ease-in-out infinite alternate;
+}
+
@keyframes fadeIn {
from {
opacity: 0;
@@ -13,3 +31,22 @@
transform: translateY(0);
}
}
+
+@keyframes loadingPulse {
+ from {
+ opacity: 0.35;
+ transform: scale(0.8);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .fade,
+ .loadingDot {
+ animation: none;
+ }
+}
diff --git a/src/components/layout/RouteTransition.test.tsx b/src/components/layout/RouteTransition.test.tsx
index 24dfb45..4905ba7 100644
--- a/src/components/layout/RouteTransition.test.tsx
+++ b/src/components/layout/RouteTransition.test.tsx
@@ -1,4 +1,5 @@
-import { render, screen } from '@testing-library/react'
+import { lazy, type ComponentType } from 'react'
+import { act, render, screen } from '@testing-library/react'
import { createMemoryRouter, RouterProvider } from 'react-router-dom'
import { describe, expect, it } from 'vitest'
import { RouteTransition } from './RouteTransition'
@@ -13,4 +14,27 @@ describe('RouteTransition', () => {
expect(screen.getByText('dashboard content')).toBeInTheDocument()
})
+
+ it('announces route loading while a lazy page chunk is resolving', async () => {
+ let resolvePage!: (module: { default: ComponentType }) => void
+ const LazyPage = lazy(
+ () =>
+ new Promise<{ default: ComponentType }>((resolve) => {
+ resolvePage = resolve
+ }),
+ )
+ const router = createMemoryRouter(
+ [{ element: , children: [{ path: '/tasks', element: }] }],
+ { initialEntries: ['/tasks'] },
+ )
+ render()
+
+ expect(screen.getByRole('status')).toHaveTextContent('화면을 불러오는 중입니다')
+
+ await act(async () => {
+ resolvePage({ default: () =>
lazy task content
})
+ })
+
+ expect(await screen.findByText('lazy task content')).toBeInTheDocument()
+ })
})
diff --git a/src/components/layout/RouteTransition.tsx b/src/components/layout/RouteTransition.tsx
index e1c60d3..c041c5e 100644
--- a/src/components/layout/RouteTransition.tsx
+++ b/src/components/layout/RouteTransition.tsx
@@ -1,3 +1,4 @@
+import { Suspense } from 'react'
import { Outlet, useLocation } from 'react-router-dom'
import styles from './RouteTransition.module.css'
@@ -6,7 +7,16 @@ export function RouteTransition() {
return (
-
+
+
+ 화면을 불러오는 중입니다
+
+ }
+ >
+
+
)
}
diff --git a/src/routes.tsx b/src/routes.tsx
index bfe3436..6d5f3d0 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -1,12 +1,7 @@
+import { lazy } from 'react'
import { createBrowserRouter } from 'react-router-dom'
import { RequireAuth } from './components/auth/RequireAuth'
import { AppLayout } from './components/layout/AppLayout'
-import { AgentLogPage } from './pages/AgentLogPage/AgentLogPage'
-import { CaseDetailPage } from './pages/CaseDetailPage/CaseDetailPage'
-import { CreateWorkPage } from './pages/CreateWorkPage/CreateWorkPage'
-import { DashboardPage } from './pages/DashboardPage/DashboardPage'
-import { DocumentDetailPage } from './pages/DocumentDetailPage/DocumentDetailPage'
-import { DocumentListPage } from './pages/DocumentListPage/DocumentListPage'
import { EmailSentPage } from './pages/EmailSentPage/EmailSentPage'
import { ForgotPasswordPage } from './pages/ForgotPasswordPage/ForgotPasswordPage'
import { IntroPage } from './pages/IntroPage/IntroPage'
@@ -16,16 +11,71 @@ import { LinkUploadPage } from './pages/LinkUploadPage/LinkUploadPage'
import { LoginPage } from './pages/LoginPage/LoginPage'
import { NotFoundPage } from './pages/NotFoundPage/NotFoundPage'
import { OnboardingImportPage } from './pages/OnboardingImportPage/OnboardingImportPage'
-import { ProfilePage } from './pages/ProfilePage/ProfilePage'
import { ResetCompletePage } from './pages/ResetCompletePage/ResetCompletePage'
import { ResetPasswordPage } from './pages/ResetPasswordPage/ResetPasswordPage'
-import { ReviewWorkPage } from './pages/ReviewWorkPage/ReviewWorkPage'
import { SignupPage } from './pages/SignupPage/SignupPage'
-import { TicketDetailPage } from './pages/TicketDetailPage/TicketDetailPage'
-import { TicketListPage } from './pages/TicketListPage/TicketListPage'
-import { WorkerDetailPage } from './pages/WorkerDetailPage/WorkerDetailPage'
-import { WorkerListPage } from './pages/WorkerListPage/WorkerListPage'
-import { WorkListPage } from './pages/WorkListPage/WorkListPage'
+
+const AgentLogPage = lazy(() =>
+ import('./pages/AgentLogPage/AgentLogPage').then((module) => ({ default: module.AgentLogPage })),
+)
+const CaseDetailPage = lazy(() =>
+ import('./pages/CaseDetailPage/CaseDetailPage').then((module) => ({
+ default: module.CaseDetailPage,
+ })),
+)
+const CreateWorkPage = lazy(() =>
+ import('./pages/CreateWorkPage/CreateWorkPage').then((module) => ({
+ default: module.CreateWorkPage,
+ })),
+)
+const DashboardPage = lazy(() =>
+ import('./pages/DashboardPage/DashboardPage').then((module) => ({
+ default: module.DashboardPage,
+ })),
+)
+const DocumentDetailPage = lazy(() =>
+ import('./pages/DocumentDetailPage/DocumentDetailPage').then((module) => ({
+ default: module.DocumentDetailPage,
+ })),
+)
+const DocumentListPage = lazy(() =>
+ import('./pages/DocumentListPage/DocumentListPage').then((module) => ({
+ default: module.DocumentListPage,
+ })),
+)
+const ProfilePage = lazy(() =>
+ import('./pages/ProfilePage/ProfilePage').then((module) => ({ default: module.ProfilePage })),
+)
+const ReviewWorkPage = lazy(() =>
+ import('./pages/ReviewWorkPage/ReviewWorkPage').then((module) => ({
+ default: module.ReviewWorkPage,
+ })),
+)
+const TicketDetailPage = lazy(() =>
+ import('./pages/TicketDetailPage/TicketDetailPage').then((module) => ({
+ default: module.TicketDetailPage,
+ })),
+)
+const TicketListPage = lazy(() =>
+ import('./pages/TicketListPage/TicketListPage').then((module) => ({
+ default: module.TicketListPage,
+ })),
+)
+const WorkerDetailPage = lazy(() =>
+ import('./pages/WorkerDetailPage/WorkerDetailPage').then((module) => ({
+ default: module.WorkerDetailPage,
+ })),
+)
+const WorkerListPage = lazy(() =>
+ import('./pages/WorkerListPage/WorkerListPage').then((module) => ({
+ default: module.WorkerListPage,
+ })),
+)
+const WorkListPage = lazy(() =>
+ import('./pages/WorkListPage/WorkListPage').then((module) => ({
+ default: module.WorkListPage,
+ })),
+)
export const router = createBrowserRouter([
{ path: '/', element: , errorElement: },