Ch 01. fetch()의 재발명 — Next.js 확장 fetch
익숙한 얼굴, 새로운 능력
fetch는 웹 개발자에게 너무나 익숙한 API입니다. URL을 넣으면 응답이 나옵니다. MDN에서 찾아볼 수 있는, 표준 Web API입니다.
Next.js는 이 fetch를 그대로 씁니다. 하지만 내부적으로 확장해두었습니다. 캐싱과 재검증 기능이 추가되었습니다. 기존 코드는 그대로 동작하면서, 새로운 옵션을 사용하면 강력한 기능을 쓸 수 있습니다.
Server Component에서 async/await로 데이터 가져오기
과거에는 데이터를 가져오기 위해 useEffect와 useState를 조합해야 했습니다. App Router에서는 Server Component를 async 함수로 만들고 await를 바로 쓸 수 있습니다.
// 파일: app/posts/page.tsx
interface Post {
id: number;
title: string;
excerpt: string;
author: string;
}
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!res.ok) {
throw new Error(`게시글을 가져오지 못했습니다: ${res.status}`);
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<main>
<h1>게시글 목록</h1>
<ul>
{posts.slice(0, 10).map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</main>
);
}
```text
`getPosts` 함수는 일반 `async` 함수입니다. 페이지 컴포넌트에서 `await`로 호출합니다. 깔끔하고 직관적입니다.
### 자동 캐싱 기본 동작
Next.js의 `fetch`는 기본적으로 응답을 캐시합니다. 첫 요청에서 응답을 받아 캐시에 저장하고, 이후 같은 URL로 요청이 오면 캐시에서 반환합니다. `cache: 'force-cache'`가 기본값입니다.
이 동작은 SSG와 유사합니다. 빌드 후 데이터가 바뀌어도 캐시된 응답이 계속 사용됩니다.
### 병렬 fetch로 성능 최적화
여러 데이터를 순차적으로 가져오면 시간이 더 걸립니다. `Promise.all`로 병렬 처리하면 전체 시간을 단축할 수 있습니다.
```tsx
// 파일: app/dashboard/page.tsx
interface User {
id: number;
name: string;
email: string;
}
interface Stats {
posts: number;
comments: number;
likes: number;
}
async function getUser(id: number): Promise<User> {
const res = await fetch(`https://api.example.com/users/${id}`, {
cache: 'no-store',
});
return res.json();
}
async function getUserStats(id: number): Promise<Stats> {
const res = await fetch(`https://api.example.com/users/${id}/stats`, {
cache: 'no-store',
});
return res.json();
}
export default async function DashboardPage() {
// 순차 실행: user 완료 후 stats 시작 → 총 시간 = user 시간 + stats 시간
// const user = await getUser(1);
// const stats = await getUserStats(1);
// 병렬 실행: 동시에 시작 → 총 시간 = max(user 시간, stats 시간)
const [user, stats] = await Promise.all([getUser(1), getUserStats(1)]);
return (
<div>
<h1>{user.name}님의 대시보드</h1>
<p>게시글: {stats.posts}개</p>
<p>댓글: {stats.comments}개</p>
<p>좋아요: {stats.likes}개</p>
</div>
);
}
두 요청이 서로 의존하지 않는다면 항상 Promise.all로 병렬 처리하는 것이 좋습니다.
fetch 중복 요청 자동 제거
같은 URL과 옵션으로 여러 곳에서 fetch를 호출해도, Next.js는 자동으로 중복을 제거합니다. 같은 요청이 들어오면 하나의 HTTP 요청만 보내고 결과를 공유합니다.
레이아웃과 페이지 양쪽에서 같은 사용자 정보를 가져오더라도 실제 HTTP 요청은 한 번만 발생합니다.
다음 챕터에서는
다음 챕터에서는 fetch의 캐시 옵션을 깊이 살펴봅니다. force-cache, no-store, revalidate 세 가지 옵션이 각각 SSG, SSR, ISR에 어떻게 대응하는지 배웁니다.