로딩 UI (Loading UI)
데이터 로딩 중 사용자에게 보여줄 로딩 UI를 구현하는 방법을 배웁니다.
Next.js 기초 입문 12회차: 로딩 UI (Loading UI)
안녕하세요. Next.js 기초 입문 과정의 12번째 학습 시간입니다. 이번 회차에서는 사용자 경험을 크게 향상시킬 수 있는 로딩 UI(Loading User Interface) 구현 방법에 대해 심도 있게 학습하겠습니다. 웹 애플리케이션에서 데이터를 불러오는 동안 사용자에게 시각적인 피드백을 제공하는 것은 매우 중요합니다.
Next.js는 React의 강력한 기능인 Suspense를 활용하여 이러한 로딩 상태를 효과적으로 관리할 수 있는 방법을 제공합니다.
📌 이번 회차 학습 목표
- Next.js에서 로딩 UI의 필요성을 이해하고 설명할 수 있습니다.
- React의
Suspense컴포넌트를 사용하여 데이터 로딩 상태를 관리할 수 있습니다. loading.js파일을 활용하여 라우트별 로딩 UI를 구현할 수 있습니다.- 스트리밍(Streaming)과 점진적 렌더링(Progressive Rendering) 개념을 이해하고 적용할 수 있습니다.
- 사용자에게 더 나은 경험을 제공하는 로딩 전략을 수립할 수 있습니다.
📝 개념 설명
1. 로딩 UI의 중요성
현대 웹 애플리케이션은 사용자에게 풍부한 데이터를 제공하며, 이 데이터는 대부분 네트워크를 통해 비동기적으로 로드됩니다. 데이터 로딩에는 시간이 소요될 수 있으며, 이 시간 동안 사용자에게 아무런 시각적 피드백이 없다면 사용자는 애플리케이션이 멈췄다고 오해하거나 답답함을 느낄 수 있습니다. 로딩 UI는 이러한 대기 시간 동안 사용자에게 애플리케이션이 정상적으로 작동하고 있음을 알리고, 다음 콘텐츠가 곧 나타날 것이라는 기대를 심어주어 사용자 경험(UX)을 크게 향상시킵니다.
💡 팁: 로딩 UI는 단순히 ‘기다리세요’라는 메시지 이상입니다. 이는 사용자의 인내심을 유지시키고, 애플리케이션의 반응성을 높이는 중요한 요소입니다.
2. React Suspense와 Next.js의 통합
Next.js는 React의 Suspense 컴포넌트를 활용하여 데이터 로딩 UI를 구현하는 강력한 메커니즘을 제공합니다. Suspense는 비동기 작업(예: 데이터 페칭)이 완료될 때까지 특정 UI(fallback)를 렌더링하고, 작업이 완료되면 실제 콘텐츠를 보여주는 방식으로 작동합니다. Next.js 13부터는 App Router와 함께 loading.js 파일을 통해 라우트 세그먼트별로 Suspense를 쉽게 적용할 수 있게 되었습니다.
3. loading.js 파일 규칙
Next.js의 App Router에서는 특정 라우트 세그먼트 내에 loading.js 파일을 생성함으로써 해당 세그먼트의 데이터 로딩 중에 자동으로 로딩 UI를 표시할 수 있습니다. 이 파일은 React Server Component로 작성되어야 하며, Suspense 컴포넌트의 fallback으로 사용됩니다.
- 위치:
app/dashboard/loading.js와 같이 라우트 세그먼트 폴더 내에 위치합니다. - 작동 방식: 해당 라우트 세그먼트의 데이터 페칭이 완료되기 전까지
loading.js에 정의된 UI가 즉시 렌더링됩니다. - 장점: 복잡한 Suspense 트리 구조를 직접 작성할 필요 없이, 파일 시스템 기반으로 간편하게 로딩 UI를 관리할 수 있습니다.
4. 스트리밍(Streaming)과 점진적 렌더링(Progressive Rendering)
Next.js의 loading.js와 Suspense는 스트리밍과 점진적 렌더링을 가능하게 합니다. 이는 서버에서 HTML을 여러 청크(chunk)로 나누어 클라이언트로 점진적으로 전송하는 기술입니다. 데이터 로딩이 느린 부분은 로딩 UI를 먼저 보여주고, 빠르게 로드되는 부분은 즉시 렌더링하여 사용자에게 콘텐츠가 점진적으로 나타나는 경험을 제공합니다.
- 스트리밍: 서버에서 클라이언트로 데이터를 연속적으로 전송하는 방식.
- 점진적 렌더링: 페이지의 일부가 준비되는 대로 렌더링하여 사용자에게 콘텐츠가 서서히 나타나는 것처럼 보이게 하는 방식.
loading.js UI 즉시 스트리밍💡 예제 & 실습
Next.js 프로젝트에서 loading.js 파일을 사용하여 로딩 UI를 구현하는 실습을 진행하겠습니다.
실습 1: 기본 loading.js 구현
사용자 목록을 비동기적으로 불러오는 페이지를 만들고, 데이터 로딩 중에는 간단한 로딩 스피너를 표시합니다.
새 Next.js 프로젝트를 생성하거나 기존 프로젝트를 사용합니다.
npx create-next-app@latest my-loading-app
cd my-loading-app
app/users 폴더를 생성하고 그 안에 page.js 파일을 만듭니다. 이 파일은 서버 컴포넌트이며, 비동기적으로 사용자 데이터를 가져옵니다.
// app/users/page.js
import React from 'react';
async function getUsers() {
// 실제 API 호출을 시뮬레이션하기 위해 2초 지연을 추가합니다.
await new Promise(resolve => setTimeout(resolve, 2000));
const res = await fetch('https://jsonplaceholder.typicode.com/users');
if (!res.ok) {
throw new Error('Failed to fetch users');
}
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<div>
<h1>사용자 목록</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
}
3단계: loading.js 파일 생성
app/users 폴더 안에 loading.js 파일을 생성합니다. 이 파일은 UsersPage 컴포넌트의 데이터 로딩 중에 표시될 UI를 정의합니다.
// app/users/loading.js
import React from 'react';
export default function Loading() {
return (
<div style={{ textAlign: 'center', padding: '20px' }}>
<h2>사용자 데이터를 불러오는 중...</h2>
<div style={{
border: '4px solid #f3f3f3',
borderTop: '4px solid #3498db',
borderRadius: '50%',
width: '40px',
height: '40px',
animation: 'spin 1s linear infinite',
margin: '20px auto'
}}></div>
<style jsx>{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}</style>
</div>
);
}
개발 서버를 시작하고 http://localhost:3000/users로 접속합니다.
npm run dev
페이지에 접속하면 약 2초 동안 ‘사용자 데이터를 불러오는 중…’ 메시지와 함께 스피너가 표시된 후, 사용자 목록이 나타나는 것을 확인할 수 있습니다. 이는 loading.js 파일이 UsersPage의 데이터 로딩을 기다리는 동안 fallback UI로 작동했음을 의미합니다.
loading.js의 역할loading.js 생성.실습 2: 중첩된 라우트에서의 로딩 UI
중첩된 라우트 구조에서 각 세그먼트별로 독립적인 로딩 UI를 구현하는 방법을 살펴봅니다.
app/users/[id] 폴더를 생성하고 page.js 및 loading.js 파일을 추가합니다.
// app/users/[id]/page.js
import React from 'react';
async function getUser(id) {
await new Promise(resolve => setTimeout(resolve, 3000)); // 3초 지연
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!res.ok) {
throw new Error('Failed to fetch user');
}
return res.json();
}
export default async function UserDetailPage({ params }) {
const user = await getUser(params.id);
return (
<div>
<h1>사용자 상세 정보</h1>
<p><strong>이름:</strong> {user.name}</p>
<p><strong>이메일:</strong> {user.email}</p>
<p><strong>전화번호:</strong> {user.phone}</p>
<p><strong>웹사이트:</strong> {user.website}</p>
</div>
);
}
// app/users/[id]/loading.js
import React from 'react';
export default function UserDetailLoading() {
return (
<div style={{ textAlign: 'center', padding: '30px', border: '1px dashed #ccc' }}>
<h3>사용자 상세 정보를 불러오는 중...</h3>
<p>잠시만 기다려 주세요.</p>
</div>
);
}
http://localhost:3000/users/1과 같은 URL로 접속하면, 먼저 app/users/loading.js가 표시된 후, 사용자 목록이 나타납니다. 그 상태에서 /users/1로 이동하면 app/users/[id]/loading.js에 정의된 로딩 UI가 표시된 후, 상세 정보가 로드됩니다. 이는 각 라우트 세그먼트가 독립적으로 로딩 상태를 관리할 수 있음을 보여줍니다.
⚠️ 자주 틀리는 것 / 주의사항
loading.js는 서버 컴포넌트여야 합니다:loading.js파일은 기본적으로 서버 컴포넌트로 간주됩니다. 클라이언트 컴포넌트 기능을 사용하려면'use client'지시어를 명시해야 하지만, 로딩 UI 자체는 서버에서 빠르게 렌더링되어야 하므로 서버 컴포넌트로 유지하는 것이 일반적입니다.loading.js는 해당 세그먼트의 모든 데이터를 기다립니다:loading.js는 해당 라우트 세그먼트(및 그 하위의 모든 서버 컴포넌트)의 데이터 로딩이 완료될 때까지 표시됩니다. 만약 특정 컴포넌트만 로딩 UI를 가지고 싶다면, 해당 컴포넌트를Suspense로 감싸는 것이 더 적절할 수 있습니다.- 데이터 페칭 위치: 데이터 페칭은 서버 컴포넌트 내부에서 직접
await키워드를 사용하여 수행하는 것이 Next.js의 Suspense와 가장 잘 통합됩니다. 클라이언트 컴포넌트에서useEffect와 같은 훅을 사용하여 데이터를 가져오는 경우,loading.js는 해당 로딩 상태를 감지하지 못할 수 있습니다. - 너무 복잡한 로딩 UI 지양: 로딩 UI는 빠르게 렌더링되어야 합니다. 너무 복잡하거나 무거운 로딩 UI는 오히려 사용자 경험을 저해할 수 있습니다. 간단하고 명확한 피드백을 제공하는 것이 좋습니다.
- 로딩 UI의 중요성: 사용자에게 데이터 로딩 중임을 알리고, 대기 시간을 관리하여 사용자 경험을 향상시킵니다.
loading.js파일: Next.js App Router에서 특정 라우트 세그먼트의 데이터 로딩 중 자동으로 표시되는 UI를 정의합니다.- Suspense 기반:
loading.js는 React의Suspense컴포넌트의fallback으로 작동하여 스트리밍 및 점진적 렌더링을 가능하게 합니다. - 스트리밍 및 점진적 렌더링: 서버에서 HTML을 청크 단위로 전송하여, 준비된 부분부터 먼저 렌더링함으로써 사용자에게 콘텐츠가 점진적으로 나타나게 합니다.
- 주의사항:
loading.js는 서버 컴포넌트이며, 해당 세그먼트의 모든 데이터 로딩을 기다립니다. 간결하고 빠른 로딩 UI를 목표로 해야 합니다.
🔗 다음 회차 예고
이번 회차에서는 데이터 로딩 중 사용자에게 시각적인 피드백을 제공하는 로딩 UI 구현 방법을 학습했습니다. 다음 13회차에서는 에러 처리 (Error Handling)에 대해 다룰 예정입니다. 애플리케이션에서 발생할 수 있는 다양한 에러 상황을 효과적으로 감지하고, 사용자에게 친화적인 에러 메시지를 표시하는 방법을 배우게 될 것입니다. 로딩 UI와 마찬가지로, 에러 처리 역시 사용자 경험을 크게 좌우하는 중요한 요소입니다.