Next.js 14 Suspense를 활용한 로딩 UI 구현
React Suspense와 Next.js의 로딩 컨벤션을 활용하여 사용자 경험을 향상시키는 방법을 알아봅니다.
Next.js 14 Suspense를 활용한 로딩 UI 구현
Introduction
Suspense는 비동기 작업이 완료될 때까지 로딩 상태를 표시하는 React 컴포넌트입니다. Next.js 14에서는 Suspense와 결합된 스트리밍 서버 렌더링으로 사용자 경험을 크게 향상시킬 수 있습니다. 이 글에서는 Suspense 활용법과 로딩 UI 패턴을 다룹니다.
Environment
# 프로젝트 구조
my-app/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── loading.tsx
│ ├── dashboard/
│ │ ├── page.tsx
│ │ └── loading.tsx
│ └── components/
│ ├── ProductList.tsx
│ └── UserProfile.tsx
└── package.json
# 기술 스택
Next.js: 14.2.5
React: 18.3.1Problem
전체 페이지 로딩은 사용자 경험을 저해합니다:
// ❌ 전체 페이지가 로드될 때까지 빈 화면
export default async function DashboardPage() {
// 느린 데이터 페칭
const stats = await fetchStats();
const notifications = await fetchNotifications();
const userData = await fetchUserData();
return (
);
}Analysis
Suspense의 장점:
- 점진적 로딩: 컴포넌트별로 독립적으로 로드
- 스트리밍: HTML을 chunks로 전송
- 用户体验: 로딩 상태를 즉시 표시
- 에러 격리: 개별 컴포넌트 실패가 전체에 영향 안 함
Solution
1. 기본 Suspense 사용
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { Stats } from '@/components/Stats';
import { Notifications } from '@/components/Notifications';
import { UserProfile } from '@/components/UserProfile';
export default function DashboardPage() {
return (
대시보드
통계 로딩 중... }>
알림 로딩 중... 2. 파일 기반 로딩 UI
// app/dashboard/loading.tsx
export default function Loading() {
return (
);
}// app/dashboard/page.tsx
import { Stats } from '@/components/Stats';
export default function DashboardPage() {
return (
대시보드
);
}3. 스트리밍과 Suspense 조합
// app/page.tsx
import { Suspense } from 'react';
import { Headers } from '@/components/Headers';
import { MainContent } from '@/components/MainContent';
import { Sidebar } from '@/components/Sidebar';
export default function HomePage() {
return (
{/* 즉시 렌더링 (동기) */}
{/* 스트리밍으로 로드 */}
}>
{/* 스트리밍으로 로드 */}
}>
);
}4. Suspense를 이용한 에러 격리
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { ErrorBoundary } from '@/components/ErrorBoundary';
import { CriticalStats } from '@/components/CriticalStats';
import { OptionalFeatures } from '@/components/OptionalFeatures';
export default function DashboardPage() {
return (
대시보드
{/* 에러 바운더리로 격리 */}
통계 로딩 실패 }>
로딩 중...