이 페이지에서는 Server Component와 Client Component에서 데이터를 가져오는 방법, 그리고 데이터에 의존하는 컴포넌트를 스트리밍하는 방법을 안내한다.
Server Component에서는 다음과 같은 모든 비동기 I/O를 사용해 데이터를 가져올 수 있다.
fetch API로 데이터를 가져오려면 컴포넌트를 비동기 함수로 만들고 fetch 호출을 await 하면 된다.
app/blog/page.tsx
export default async function Page() {
const data = await fetch('<https://api.vercel.app/blog>')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
참고
fetch응답은 기본적으로 캐시되지 않는다. 그러나 Next.js는 해당 라우트를 프리렌더링하고 출력 결과를 성능 향상읠 위해 캐시한다. 동적 렌더링을 사용하려면{ cache: ‘no-store’ }옵션을 사용하자- 개발 중에는
fetch호출을 로깅하여 더 잘 보이도록 할 수 있다.
Server Component는 서버에서 렌더링되므로 ORM이나 DB 클라이언트로 직접 쿼리를 수행해도 안전하다. 컴포넌트를 async 함수로 만들고 쿼리를 await 하자.
app/blog/page.tsx
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Node.js의 fs 같은 API를 사용해 파일 시스템 읽기
Client Component에서 데이터를 가져오는 방법은 두가지이다.
React's use hook
use hook을 사용해 서버에서 클라이언트로 데이러를 스트리밍할 수 있다.
Server Component에서 데이터를 먼저 가져오고 promis를 Client Component에 prop 으로 전달하면 된다.
app/blog/page.tsx
import Posts from '@/app/ui/posts'
import { Suspense } from 'react'
export default function Page() {
// 데이터를 await 하지 않음
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
그 다음, Client Component에서 use hook으로 Promise 를 읽어보자.
app/ui/posts.tsx
'use client'
import { use } from 'react'
export default function Posts({
posts,
}: {
posts: Promise<{ id: string; title: string }[]>
}) {
const allPosts = use(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
위 예시에서 <Posts> 는 <Suspense> 로 감싸져 있으므로 Promise 가 해결되는 동안 fallback이 표시된다.
SWR, React Query 같은 커뮤니티 라이브러리 사용
Client Component에서 SWR이나 React Query같은 외부 라이브러리를 사용할 수도 있다. 각 라이브러리는 자체적인 캐싱, 스트리밍 등의 규칙을 갖는다.
app/blog/page.tsx (SWR 사용 시)
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'<https://api.vercel.app/blog>',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post: { id: string; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
요청을 중복해서 보내지 않도록 하는 한 가지 방법은 요청 메모이제이션을 사용하는 것이다. 이 메커니즘을 사용하면 동일한 URL과 옵션을 가진 GET 또는 HEAD fetch 요청은 한 번의 렌더링 과정에서 하나의 요청으로 합쳐진다.
이 기능은 자동으로 적용되며 원치 않는 경우 Abort Signal 을 fetch 에 전달해 사용을 끌 수 있다.
요청 메모이제이션은 단일 요청이 처리되는 동안만 적용된다.
또한 Next.js의 Data Cache를 사용해 fetch 요청 중복을 줄일 수도 있다. 예를 들어, fetch 옵션에 cache: ‘force-cache’ 를 설정하면 된다.
Data Cache는 현재 렌더링 과정과 이후 들어오는 요청 간에 데이터를 공유할 수 있게 해준다.
만약 fetch를 사용하지 않고 ORM이나 데이터베이스를 직접 사용하고 있다면, React의 cache 함수를 사용해 데이터 접근 로직을 감싸주면 된다.