데이터 페칭 (Server Component)
서버 컴포넌트에서 데이터를 효율적으로 가져오는 방법을 배웁니다.
9회차 주제: 데이터 페칭 (Server Component)
회차 설명: 서버 컴포넌트에서 데이터를 효율적으로 가져오는 방법을 배웁니다.
난이도: 2/5
키워드: 서버 데이터 페칭, async/await, 데이터 직렬화
안녕하세요! Next.js 기초 입문 과정 9회차에 오신 것을 환영합니다. 이번 회차에서는 Next.js의 핵심 기능 중 하나인 서버 컴포넌트(Server Component)에서 데이터를 효율적으로 가져오는 방법에 대해 심도 있게 학습합니다. 특히 async/await 문법을 활용한 비동기 데이터 처리와 데이터 직렬화(Serialization)의 중요성을 중점적으로 다룰 예정입니다.
📌 이번 회차 학습 목표
- Next.js 서버 컴포넌트의 데이터 페칭 원리를 이해할 수 있습니다.
async/await문법을 사용하여 서버 컴포넌트 내에서 비동기 데이터를 가져올 수 있습니다.- 데이터 직렬화의 필요성과 그 의미를 설명할 수 있습니다.
- 실제 API 호출 예제를 통해 서버 컴포넌트 데이터 페칭을 구현할 수 있습니다.
- 클라이언트 컴포넌트와의 데이터 페칭 방식 차이를 인지할 수 있습니다.
📝 개념 설명
1. 서버 컴포넌트와 데이터 페칭의 기본 원리
Next.js 13부터 도입된 서버 컴포넌트(Server Component)는 서버에서 렌더링되고 데이터를 가져오는(fetching) 컴포넌트입니다. 이는 클라이언트 측에서 데이터를 가져오는 기존 방식과 달리, 서버에서 직접 데이터베이스나 외부 API에 접근하여 데이터를 가져온 후, 렌더링된 HTML을 클라이언트에 전송하는 방식으로 동작합니다.
이러한 방식의 주요 이점은 다음과 같습니다:
- 성능 향상: 서버에서 데이터를 미리 가져오므로 클라이언트가 데이터를 요청하고 기다리는 시간을 줄일 수 있습니다.
- 보안 강화: 민감한 데이터나 API 키를 클라이언트 번들에 노출하지 않고 서버에서 안전하게 처리할 수 있습니다.
- 번들 크기 감소: 클라이언트 측 자바스크립트 번들에 데이터 페칭 로직이 포함되지 않아 번들 크기가 줄어듭니다.
- SEO 최적화: 검색 엔진 크롤러가 완전하게 렌더링된 페이지를 받아볼 수 있어 SEO에 유리합니다.
2. async/await를 활용한 비동기 데이터 페칭
서버 컴포넌트에서는 자바스크립트의 async/await 문법을 사용하여 비동기적으로 데이터를 가져올 수 있습니다. 이는 컴포넌트 함수를 async로 선언하고, 데이터 페칭 로직 앞에 await 키워드를 붙여 해당 작업이 완료될 때까지 기다리도록 지시하는 방식입니다.
// app/page.tsx (서버 컴포넌트)
async function getPosts() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
// 응답이 성공적인지 확인
if (!res.ok) {
// 에러 처리
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const posts = await getPosts(); // await를 사용하여 데이터가 올 때까지 기다립니다.
return (
<main>
<h1>블로그 게시물</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
);
}
위 코드에서 getPosts 함수는 fetch API를 사용하여 외부 데이터를 비동기적으로 가져옵니다. Page 컴포넌트 역시 async로 선언되어 await getPosts()를 통해 데이터 페칭이 완료될 때까지 기다린 후, 받아온 데이터를 사용하여 UI를 렌더링합니다.
3. 데이터 직렬화(Serialization)의 중요성
서버 컴포넌트에서 데이터를 가져올 때 데이터 직렬화(Serialization)는 매우 중요한 개념입니다. 직렬화란 데이터 구조나 객체를 저장하거나 전송할 수 있는 형식(예: 문자열, JSON)으로 변환하는 과정을 의미합니다.
Next.js 서버 컴포넌트에서 데이터를 가져오면, 이 데이터는 서버에서 클라이언트로 전송되어야 합니다. 이때, 자바스크립트 객체나 특정 타입의 데이터는 웹을 통해 직접 전송될 수 없으므로, JSON과 같은 표준 직렬화 가능한 형식으로 변환되어야 합니다. Next.js는 기본적으로 서버 컴포넌트에서 반환되는 데이터를 자동으로 직렬화하여 클라이언트로 전송합니다.
그러나 직렬화할 수 없는 특정 타입의 데이터(예: Date 객체, Map, Set, 함수 등)를 직접 반환하려고 하면 에러가 발생할 수 있습니다. 따라서 서버 컴포넌트에서 데이터를 페칭할 때는 항상 직렬화 가능한 형태로 데이터를 가공하여 반환해야 합니다.
4. 캐싱(Caching) 동작
Next.js는 fetch API를 사용하여 데이터를 가져올 때 자동으로 데이터를 캐싱합니다. 이는 동일한 데이터를 여러 번 요청할 때 불필요한 네트워크 요청을 줄여 성능을 최적화하는 데 도움을 줍니다.
- 기본 캐싱:
fetch요청은 기본적으로force-cache옵션과 유사하게 동작하여, 이전에 가져온 데이터를 재사용합니다. - 캐시 무효화:
revalidate옵션이나cache: 'no-store'옵션을 사용하여 캐시 동작을 제어할 수 있습니다.
// 캐시를 사용하지 않는 경우
const res = await fetch('https://api.example.com/data', { cache: 'no-store' });
// 일정 시간(초)마다 캐시를 재검증하는 경우
const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } });
💡 예제 & 실습
이번 실습에서는 Next.js 서버 컴포넌트에서 외부 API를 호출하여 사용자 목록을 가져오고 화면에 표시하는 과정을 단계별로 구현해 보겠습니다.
실습 목표: JSONPlaceholder API에서 사용자 데이터를 가져와 목록으로 표시하기
npx create-next-app@latest my-data-fetching-app
cd my-data-fetching-app
app 디렉토리 안에 users라는 새 폴더를 만들고, 그 안에 page.tsx 파일을 생성합니다. 이 파일은 /users 경로에 접근했을 때 렌더링될 서버 컴포넌트가 됩니다.
// app/users/page.tsx
interface User {
id: number;
name: string;
username: string;
email: string;
}
async function getUsers(): Promise<User[]> {
// JSONPlaceholder API에서 사용자 데이터를 가져옵니다.
const res = await fetch('https://jsonplaceholder.typicode.com/users');
// 응답이 성공적인지 확인합니다.
if (!res.ok) {
// 에러 발생 시 throw Error를 사용하여 에러를 상위로 전파합니다.
throw new Error('Failed to fetch users data');
}
// JSON 형태로 파싱하여 반환합니다.
return res.json();
}
export default async function UsersPage() {
// getUsers 함수를 호출하여 사용자 데이터를 비동기적으로 가져옵니다.
const users = await getUsers();
return (
<div style={{ padding: '20px' }}>
<h1>사용자 목록 (Server Component)</h1>
<ul>
{users.map((user) => (
<li key={user.id} style={{ marginBottom: '10px', border: '1px solid #eee', padding: '10px', borderRadius: '5px' }}>
<strong>이름:</strong> {user.name} (<em>{user.username}</em>)<br />
<strong>이메일:</strong> {user.email}
</li>
))}
</ul>
</div>
);
}
User인터페이스는 가져올 사용자 데이터의 타입을 정의합니다. 이는 TypeScript를 사용할 때 코드의 안정성을 높여줍니다.getUsers함수는async로 선언되어 비동기 작업을 수행합니다.fetchAPI를 사용하여 JSONPlaceholder의/users엔드포인트에서 데이터를 가져옵니다.res.ok를 통해 HTTP 응답 상태가 성공적인지 확인하고, 실패 시 에러를 발생시킵니다.res.json()을 통해 응답 본문을 JSON 형태로 파싱하여 반환합니다.UsersPage컴포넌트 역시async로 선언되어await getUsers()를 호출합니다. 이 시점에서 서버는getUsers함수가 완료될 때까지 기다렸다가, 받아온users데이터를 사용하여 HTML을 렌더링합니다.- 렌더링된 HTML은 클라이언트로 전송되며, 클라이언트 측에서는 별도의 데이터 페칭 로직 없이 바로 사용자 목록을 볼 수 있습니다.
터미널에서 다음 명령어를 실행하여 개발 서버를 시작합니다.
npm run dev
웹 브라우저에서 http://localhost:3000/users 주소로 접속하면, JSONPlaceholder API에서 가져온 사용자 목록이 화면에 표시되는 것을 확인할 수 있습니다.
⚠️ 자주 틀리는 것 / 주의사항
- 클라이언트 컴포넌트에서
async/await직접 사용: 클라이언트 컴포넌트('use client'지시어가 있는 컴포넌트)에서는 컴포넌트 함수 자체를async로 만들 수 없습니다. 클라이언트 컴포넌트에서 데이터를 가져오려면useEffect훅과useState훅을 조합하거나 SWR, React Query와 같은 클라이언트 사이드 데이터 페칭 라이브러리를 사용해야 합니다. - 직렬화 불가능한 데이터 반환: 서버 컴포넌트에서
Date객체, 함수,Map,Set등 직렬화할 수 없는 JavaScript 객체를 직접 반환하려고 하면 런타임 에러가 발생합니다. 항상 JSON으로 변환 가능한 형태로 데이터를 가공해야 합니다. - API 키 노출: 서버 컴포넌트에서 환경 변수를 통해 API 키를 사용하는 것은 안전하지만, 실수로 클라이언트 컴포넌트에
process.env.NEXT_PUBLIC_...접두사 없이 API 키를 노출하지 않도록 주의해야 합니다. 서버 컴포넌트에서는NEXT_PUBLIC_접두사가 없는 환경 변수도 안전하게 사용할 수 있습니다. - 에러 처리 누락:
fetch요청은 네트워크 문제나 서버 응답 오류 등 다양한 이유로 실패할 수 있습니다.res.ok확인과try...catch블록을 사용하여 적절한 에러 처리 로직을 구현하는 것이 중요합니다.
- Next.js 서버 컴포넌트는 서버에서 데이터를 가져와 HTML을 렌더링한 후 클라이언트에 전송합니다.
- 서버 컴포넌트에서는
async/await문법을 사용하여 비동기 데이터 페칭을 간결하게 처리할 수 있습니다. - 서버에서 클라이언트로 전송되는 데이터는 반드시 직렬화 가능한 형태(예: JSON)여야 합니다.
- Next.js는
fetchAPI를 통해 가져온 데이터를 자동으로 캐싱하여 성능을 최적화합니다. - 클라이언트 컴포넌트와 서버 컴포넌트의 데이터 페칭 방식에는 명확한 차이가 있으므로 이를 혼동하지 않도록 주의해야 합니다.
🔗 다음 회차 예고
이번 회차에서는 서버 컴포넌트의 데이터 페칭에 대해 학습했습니다. 다음 10회차에서는 ‘클라이언트 컴포넌트 데이터 페칭’을 주제로, 클라이언트 컴포넌트에서 데이터를 가져오는 다양한 방법과 useEffect, SWR, React Query와 같은 라이브러리 활용법에 대해 자세히 알아보겠습니다. 서버 컴포넌트와 클라이언트 컴포넌트의 데이터 페칭 전략을 비교하며 Next.js의 데이터 관리 능력을 한층 더 향상시킬 수 있을 것입니다.