성능
이 페이지는 현재 코드베이스가 이미 강제하고 있는 프론트엔드 성 능
가드레일, 그 계약에서 코드가 벗어난 지점, 아직 활성 원격 측정에 연결되지
않은 backlog 항목을 정리한 reference 문서입니다.
현재 성능 경계
Lumie의 현재 App Router 구조는 인증된 모든 화면에 SSR prefetch를 넓게 적용하는 형태가 아닙니다.
서버 읽기는 다음처럼 제한된 영역에 집중되어 있습니다.
app/page.tsx,app/[customId]/page.tsx,app/[customId]/layout.tsx같은 공개 테넌트 및 홈페이지 부트스트랩app/admin/layout.tsx,app/dashboard/layout.tsx,app/(onboarding)/layout.tsx의 인증, 역할, 온보딩, 메타데이터 게이트app/admin/settings/billing/page.tsx같은 일부 역할 검증 페이지
대부분의 인증된 페이지는 얇은 서버 엔트리포인트로 남아 있고, 실제 렌더링은
app/admin/students/page.tsx, app/admin/exams/page.tsx,
app/dashboard/assignments/page.tsx처럼 클라이언트 기능 아일랜드에
넘깁니다.
src/shared/lib/query-client.ts는 여전히 서버와 브라우저의 QueryClient
분리를 안전하게 유지하지만, 그렇다고 해서 모든 인증 화면에 서버 prefetch나
새 hydration 경계를 늘려야 한다는 뜻은 아닙니다. 서버 fetch는 실제
waterfall을 없애고, 한 parser와 한 query-key 기준 원본을 서버와
클라이언트가 함께 쓸 수 있을 때만 추가해야 합니다.
소스 경로
| 영역 | 소스 경로 |
|---|---|
| App Router와 루트 provider | lumie-frontend/app/layout.tsx, lumie-frontend/app/admin/layout.tsx, lumie-frontend/app/dashboard/layout.tsx, lumie-frontend/app/dashboard/DashboardShell.tsx |
| Query client 수명주기 | lumie-frontend/src/shared/lib/query-client.ts, lumie-frontend/src/shared/providers/QueryProvider.tsx |
| 로딩, 오류, 좁은 Suspense 경계 | lumie-frontend/app/loading.tsx, lumie-frontend/app/error.tsx, lumie-frontend/app/admin/loading.tsx, lumie-frontend/app/admin/error.tsx, lumie-frontend/app/dashboard/loading.tsx, lumie-frontend/app/dashboard/error.tsx |
| 무거운 차트와 코드 분할 예시 | lumie-frontend/src/features/admin-dashboard/view-dashboard/ui/ChartsSection.tsx, lumie-frontend/src/features/admin-dashboard/view-dashboard/ui/HeroKPICard.tsx, lumie-frontend/src/entities/exam/ui/StudentDetailPanel.tsx, lumie-frontend/src/features/student-detail/view-student-detail/ui/AttendanceTab.tsx, lumie-frontend/src/features/student-detail/view-student-detail/ui/ExamResultsTab.tsx |
| 이미지 정책과 avatar URL | lumie-frontend/next.config.ts, lumie-frontend/src/shared/lib/avatar.ts, lumie-frontend/src/features/profile-settings/profile-avatar/ui/ProfileAvatar.tsx, lumie-frontend/src/shared/ui/PersonCardGrid.tsx |
| 백그라운드 polling과 job 추적 | lumie-frontend/app/admin/_components/AdminShell.tsx, lumie-frontend/src/shared/lib/jobTracker.ts, lumie-frontend/src/features/grade-omr/track-omr-grading/lib/OmrJobTrackerProvider.tsx, lumie-frontend/src/features/grade-omr/track-omr-grading/api/queries.ts, lumie-frontend/src/entities/exam/providers/ReportJobTrackerProvider.tsx, lumie-frontend/src/entities/exam/api/queries.ts |
| 채팅 스트리밍 정리 | lumie-frontend/src/features/ai-chat/api/stream.ts, lumie-frontend/src/features/ai-chat/api/useStreamMessage.ts |
| 대규모 목록 렌더링 | lumie-frontend/src/features/student-management/list-students/ui/StudentList.tsx, lumie-frontend/src/features/student-management/list-students/ui/StudentListTable.tsx, lumie-frontend/src/features/staff-management/list-staff/ui/StaffList.tsx, lumie-frontend/src/features/staff-management/list-staff/ui/StaffListTable.tsx |
App Router 가드레일
app/은 얇게 유지해야 합니다.
app/layout.tsx는QueryProvider, auth-modal URL sync, 인증 모달, 전역 toaster처럼 앱 전체 provider와 셸 관심사만 소유합니다.app/admin/layout.tsx와app/dashboard/layout.tsx는 인증 리다이렉트, 메타데이터, route progress 같은 라우트 그룹 관심사를 더한 뒤 각 셸에 넘깁니다.- 파라미터를 파싱하거나 접근만 확인하는 얇은 서버 페이지는 클라이언트 데이터
orchestration을 페이지 안에 쌓지 말고
src/의 기능 아일랜드를 렌더링해야 합니다.
현재 라우트 트리는 서버 컴포넌트를 광범위한 인증 데이터 prefetch보다 셸과 인증 경계에 더 많이 사용합니다. 서버 fetch가 사용자에게 보이는 waterfall을 실제로 줄인다는 근거가 나오기 전까지는 이를 기본값으로 두세요.
지연 로딩 차트와 무거운 import
next.config.ts는 lucide-react, radix-ui, date-fns, recharts에
대해 experimental.optimizePackageImports를 켭니다. 이 목록은 실제로
fan-out이 큰 패키지에 맞춰 유지해야 하며, 범용 catch-all 목록으로
늘리면 안 됩니다.
무거운 클라이언트 전용 시각 요소의 기본 패턴은 next/dynamic과 최종
슬롯 크기에 맞는 대체 표시입니다.
ChartsSection.tsx의 관리자 대시보드 차트HeroKPICard.tsx의 장식용 sparklineStudentDetailPanel.tsx의 시험 상세 차트AttendanceTab.tsx,ExamResultsTab.tsx의 학생 상세 차트
가드레일:
- 차트 같은 무거운 UI는 루트 레이아웃이 아니라 feature 경계에서 분할합니다
ssr: false는 정말 브라우저 전용 컴포넌트일 때만 사용합니다- 대체 표시가 최종 높이를 미리 차지하게 해서 chunk 로딩 중 페이지가 재배치되지 않도록 합니다
provider 배치
provider 배치는 성능 계약의 일부입니다. 어떤 provider든 라우트 위로 올릴수록 공통 렌더링 영역이 커집니다.
- 루트 전용 provider는
app/layout.tsx에 둡니다 - 라우트 그룹 provider는
app/admin/layout.tsx,app/dashboard/layout.tsx,app/dashboard/DashboardShell.tsx에 둡니다 - feature 전용 SDK나 상태 브리지는 실제로 필요한 가장 가까운 라우트나 위젯에 남겨 둡니다
오늘 기준으로 billing 경로가 좋은 경계 예시입니다.
app/admin/settings/billing/page.tsx는 역할을 검사하고 BillingHome을
렌더링하지만, billing SDK wrapper를 전역 관리자 레이아웃까지 끌어올리지
않습니다.
로딩, 오류, Suspense 경계
기본 라우트 경계는 이미 루트, admin, dashboard 수준에 있습니다.
app/loading.tsx,app/admin/loading.tsx,app/dashboard/loading.tsx는 셸을 고려한 skeleton을 제공합니다app/error.tsx,app/admin/error.tsx,app/dashboard/error.tsx는 실패가 빈 화면으로 번지는 것을 막습니다app/layout.tsx는Suspense를AuthModalUrlSync와AuthModal주변에만 좁게 두어useSearchParams()지원이 페이지 수준 로딩이나 스트리밍 대체 UI를 삼키지 않게 합니다
새 라우트 그룹이 로딩 비용이나 복구 UX가 크게 다르다면 루트 경계에만
의존하지 말고 자체 loading.tsx, error.tsx를 추가하세요.
이미지 최적화 정책
기본 정책:
- 래스터 자산에는
next/image를 사용합니다 - 실제로 필요한 경우에만 올바른
sizes와priority를 제공합니다 - 원격 호스트는
next.config.ts에 선언된 상태를 유지합니다
DiceBear avatar는 의도적인 예외입니다. next.config.ts는 이 SVG URL을
unoptimized로 두는 이유를 설명합니다. Sharp 최적화기를 거치면 SVG가
래스터화되고, 프로세스 내 작업량이 늘며, 256 MiB pod에서 OOM이 날 수
있기 때문입니다. 이 예외는 ProfileAvatar.tsx,
PersonCardGrid.tsx 같은 avatar 표면에도 반영되어 있으며, 두 컴포넌트는
모두 src/shared/lib/avatar.ts의 getAvatarUrl(...) 결과를 렌더링합니다.
추적해야 할 계약 드리프트:
- 검토한 코드에는 여전히
src/widgets/landing/ui/Hero.tsx,AnalyticsHighlight.tsx,ConvergedPlatform.tsx,ParentReport.tsx,src/shared/ui/PostDetailCard.tsx처럼 avatar가 아닌 영역의unoptimized사용이 남아 있습니다
이 파일들은 next.config.ts의 DiceBear 전용 근거와 맞지 않으므로,
광범위하게 unoptimized를 허용하는 근거가 아니라 backlog 정리 대상으로
봐야 합니다.