Next.js의 서버 컴포넌트에서 Data Fetching의 이점
- 한 번의 왕복으로 여러개의 data fetching을 수행하여 client-server waterfall을 줄인다.
- 민감한 정보가 클라이언트에서 노출되는 것을 방지 할 수 있다.
- 애플리케이션 코드와 데이터베이스가 동일한 지역에 있는 경우 데이터 소스에 가까운 곳에서 데이터를 가져옴으로써 지연 시간을 줄일 수 있다.
- data fetch와 render를 동일한 환경에서 하여 클라이언트와 서버 간의 통신을 줄일 수 있다.
- 데이터 요청은 캐시되고 재검증 될 수 있다.
App Router에서는 page, component, layout 모두 data를 fetch 할 수 있다. 중복된 데이터 요청이 있으면 Next.js가 알아서 캐시하고 중복요청을 제거해준다.
Next.js에서 데이터를 가져오는 방법은 fetch API, ORM 또는 데이터베이스 클라이언트, Route Handlers, 클라이언트의 서버 상태관리 라이브러리로 4가지 방법이 있다.
fetch는 익숙한 Web API이며 서버 컴포넌트, 라우트 핸들러, 서버 액션에서 사용할 수 있다.
const Page = () => {
const data = await fetch('https://ex.com/...').then((res) =>
res.json(),
)
return '...'
}
export default Page
Next.js는 동일한 URL과 옵션을 가진 요청을 자동으로 메모이제이션한다. 컴포넌트 트리의 어느 위치에서든 동일한 fetch 함수를 호출할 때 한 번만 실행된다는 의미이다. 👍
데이터를 가져오면 컴포넌트 간에 props를 전달할 필요도 없고 여러 요청을 하는 성능 문제를 걱정 할 필요가 없다.
메모이제이션은 React 기능으로 fetch 요청의 GET
메서드에만 적용되고 Route handlers의 fetch 요청에는 적용되지 않는다.
Next.js에서의 cache 옵션은 서버 측 요청이 서버의 데이터 캐시와 상호 작용하는 방식을 나타낸다. 캐시하려면 cache
옵션을 force-cache
로 설정하면 되고 서버에 항상 새로운 요청을 보내고 캐시된 데이터를 사용하지 않으려면 no-store
로 설정한다.
보통 실시간 데이터를 적용해야 한다면 no-store가 적합하고 변경이 적은 데이터라면 force-cache가 적합하다.
fetch('https://ex.com/...', { cache: 'force-cache' })
fetch('https://ex.com/...', { cache: 'no-store' })
옵션 객체를 사용하여 서버의 각 요청이 고유한 영구 캐싱 동작을 설정할 수 있다.
+14버전까지는 force-cache가 기본 동작이고 15버전부터는 기본적으로 캐시되지않아 실시간 데이터에 적합한 기본 동작을 제공한다. 따라서 캐싱이 필요할 때는 force-cache
나 revalidate
옵션을 추가해야 한다.
캐시된 데이터는 두 가지 방법으로 재검증 할 수 있다.
fetch('https://ex.com/...', { next: { revalidate: 3600 } })
Data Cache는 들어오는 요청과 배포 간에 지속되며 Memoization은 요청의 수명 동안만 지속된다.
메모이제이션은 렌더링 서버에서 데이터 캐시 서버나 데이터 소스로의 네트워크 경계를 넘어야 하는 중복 요청 수를 줄이고 Data Cache를 통해 원본 데이터 소스로의 요청 수를 줄인다.
정리하자면, 메모이제이션은 특정 요청이나 함수 호출을 위한 짧은 저장 공간, 데이터 캐시는 서버와 데이터베이스 간 요청을 줄이기 위해 더 길게 데이터를 저장하는 시스템이다.
db 클라이언트를 직접 호출하여 바로 쿼리를 수행 할 수 있으며 React cache를 사용하여 데이터 요청을 메모이제이션 할 수 있다. getItem이 여러 컴포넌트에서 여러번 호출되더라도 데이터베이스에 대한 쿼리는 한 번만 수행된다.
import { cache } from 'react'
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})
Tanstack Query, SWR과 같은 서버상태관리 리아브러리를 사용하여 클라이언트 컴포넌트에서 데이터를 가져 올 수 있다. 해당 라이브러리는 데이터의 캐싱, 재검증 등 자체 API를 제공하며 Next.js는 클라이언트 컴포넌트에서 서버 상태관리 라이브러리 사용을 권장한다.
"use client"
import useGetMessages from '@/hooks/query'
export default function Component ({ id }: { id: string }) {
const { data } = useGetMessages(id);
return '...'
}
Route Handler는 특정 route에 대해 custom 요청 핸들러를 작성할 수 있게 해주며 클라이언트 컴포넌트는 해당 라우트 핸들러를 호출 할 수 있다.
import { useQuery } from '@tanstack/react-query'
const useGetMessages = ({ id }: { id: string }) => {
return useQuery({
queryKey: ['message', id],
queryFn: async () => {
const response = await fetch('api/messages')
}
})
}
export default useGetData
서버 컴포넌트는 서버에서 렌더링 되므로 서버 컴포넌트에서 라우트 핸들러를 호출 할 필요가 없다.