Suspense와 laoding.jsx의 차이가 궁금해서 알아보았다.
Suspense
- Susepnse는 비동기 컴포넌트가 렌더링 중일 때 작동한다.
- 그렇다면, 여기서 비동기 컴포넌트란 무엇일까? 비동기 컴포넌트란, 렌더링되기 전에 데이터를 가져오거나, 어떤 비동기 작업을 수행한 후에야 완전히 준비되는 컴포넌트를 말한다. 주로 Server Components, Suspense 등과 사용한다.
// Client Component에서 비동기 처리
import { useEffect, useState } from 'react';
function AsyncComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(json => setData(json));
}, []);
if (!data) return <p>Loading...</p>;
return <div>{data.message}</div>;
}
// Server Component에서 비동기 처리
// 서버 컴포넌트에서 async 사용 가능 (Next.js App Router)
async function fetchData() {
const res = await fetch('https://api.example.com/data', { cache: 'no-store' });
return res.json();
}
export default async function Page() {
const data = await fetchData();
return <div>{data.title}</div>;
}
// Suspense를 활용한 비동기 컴포넌트 로딩
// 앱이 시작될 때 즉시 로딩되는 기존 import문과 다르게, 해당 컴포넌트가 실제로 필요할 때 동적 로딩되어 앱의 초기 로딩 속도 감소
// LazyComponent가 실제로 화면에 나타날 때만 불러옴
"use client"
const LazyComponent = React.lazy(() => import('./SomeComponent'));
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<LazyComponent />
</Suspense>
);
}
위의 내용을 바탕으로, 비동기 컴포넌트에 대해서 이해했고, suspense를 활용해보았다.
import { Suspense } from "react";
import UserInfo from "./userInfo";
export default function Example1Page() {
return (
<div>
<h1>Example1Page</h1>
<Suspense fallback={<p>사용자 정보 로딩 중...</p>}>
<UserInfo />
</Suspense>
</div>
);
}
export default async function UserInfo() {
// Simulate a delay to mimic data fetching
await new Promise((resolve) => setTimeout(resolve, 2000));
return <p>사용자 정보 불러오기 완료!</p>;
}
loading.jsx는 언제 작동할까?
- loading.jsx는 클라이언트에서 fetch가 시작되는 시점에 작동하는게 아니라, App Router에서 라우트 전환 시 해당 서버 컴포넌트가 아직 로드되지 않았을 때 자동으로 보여주는 페이지 레벨 로딩 UI이다.
- loading.jsx는 서버 컴포넌트이면서 비동기 컴포넌트일 때 작동한다.
export default async function SSRLoadingPage() {
console.log("[SERVER] Fetching user...");
await new Promise((res) => setTimeout(res, 3000)); // 3초 대기
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
return (
<div>
<h1>서버 컴포넌트 페이지</h1>
<p>이름: {user.name}</p>
</div>
);
}
export default function Loading() {
return <p>서버 컴포넌트 로딩 중입니다...</p>;
}
반례
- 클라이언트 컴포넌트에서 fetch를 하면, 브라우저가 렌더링을 먼저 완료한 뒤, useEffect()가 실행되면서 데이터를 가져오기 때문에 loading.jsx는 등장하지 않는다.
- 서버 컴포넌트가 아직 완전히 렌더링되지 않았을 때 loading.jsx가 나타나지만, 클라이언트 컴포넌트에서는 이미 화면이 렌더링된 상태에서 fetch가 시작되기 때문에, Next.js가 로딩 상태를 감지하거나 loading.jsx를 보여줄 수 타이밍이 없다.
"use client";
import { useEffect, useState } from "react";
export default function ClientFetchPage() {
const [user, setUser] = useState(null);
useEffect(() => {
console.log("[CLIENT] useEffect(fetch) 시작");
fetch("https://jsonplaceholder.typicode.com/users/1")
.then((res) => res.json())
.then((data) => {
console.log("[CLIENT] fetch 완료");
setUser(data);
});
}, []);
if (!user) {
return <p>클라이언트 useEffect로 로딩 중...</p>;
}
return (
<div>
<h1>클라이언트 컴포넌트 페이지</h1>
<p>이름: {user.name}</p>
</div>
);
}
정리
상황 loading.jsx Suspense fallback
| loading.jsx | Suspense fallback | |
| 라우트 전환 시 첫 진입 | ✅ 자동 실행 | ❌ X |
| 비동기 컴포넌트 개별 로딩 제어 | ❌ X | ✅ 개별 감싸기 가능 |
| 자동 설정 | ✅ 디렉토리명 기반 자동 인식 | ❌ 직접 Suspense 작성 필요 |
| 페이지 전체 vs 컴포넌트 단위 구분 | 전체 segment 기준 | 컴포넌트별 로딩 처리 |
'Next' 카테고리의 다른 글
| mutateAsync 도입기 (0) | 2025.11.14 |
|---|---|
| Strict Mode와 useEffect의 반복 실행 이슈 정리 (0) | 2025.10.27 |
| Next.js 프로젝트 구조, src/app으로 써야 할까? (0) | 2025.10.19 |
| bodySizeLimit 설정 (0) | 2025.06.16 |