Next.js App Router - Data Fetching(3)

Minkyu Shin·2023년 6월 9일
0

Next.js

목록 보기
8/9
post-thumbnail

Next.js

nextjs.org/docs 공식문서의 내용을 번역, 정리한 내용입니다.
사진 출처는 nextjs.org 공식문서입니다.

Caching Data

Next.js에는 각 요청 기준(추천됨) 또는 전체 route segment에 대한 데이터 캐싱이 내장되어 지원된다.

Per-request Caching

fetch()

기본적으로 모든 fetch() 요청은 자동적으로 캐싱 및 중복 처리가 된다. 만약 동일한 요청을 2번 보낸다면, 2번째 요청은 첫번째 요청의 결과를 재사용하는 것이다.

async function getComments() {
  const res = await fetch('https://...') // 요청 결과는 캐싱된다.
  return res.json()
}
 
// 함수가 2번 호출되지만 결과는 한번만 fetch 된다.
const comments = await getComments() // cache MISS(캐시 메모리에 찾는 데이터가 존재하지 X -> 즉 결과를 fetch 하게 됨)
 
// 2번째 호출은 애플리케이션 어디에서든 발생해도 된다.
const comments = await getComments() // cache HIT(캐시 메모리에 찾는 데이터가 존재, 캐시 활용)

요청은 다음과 같은 조건에서 캐싱되지 않는다:

  • 동적 메소드(next/headers , export const POST 또는 이와 비슷한 것들)가 사용되고 fetch가 POST 요청일 때(또는 Authorization 이나 cookie 헤더를 사용할 때)
  • fetchCache 는 기본적으로 캐시를 건너뛰도록 설정되어 있음
  • fetchrevalidate: 0 , cache: 'no-store' 옵션이 설정되어 있을 때

fetch 를 사용하여 실행되는 요청은 revalidate 옵션으로 요청의 갱신 빈도를 설정할 수 있다.

export default async function Page() {
  // 이 데이터를 최대 10초에 한번 갱신
  const res = await fetch('https://...', { next: { revalidate: 10 } })
  const data = res.json()
  // ...
}

React cache()

React는 cache() 로 감싸진 함수 호출의 결과를 메모이징함으로써 캐싱과 중복 요청 처리가 가능하도록 해준다. 동일한 인자를 활용하여 호출된 함수는 함수를 재실행하지 않고 캐싱된 값을 재사용한다.

// utils/getUser.js
import { cache } from 'react'
 
export const getUser = cache(async (id) => {
  const user = await db.user.findUnique({ id })
  return user
})
// app/user/[id]/layout.js
import { getUser } from '@utils/getUser'
 
export default async function UserLayout({ params: { id } }) {
  const user = await getUser(id)
  // ...
}
// app/user/[id]/page.js
import { getUser } from '@utils/getUser'
 
export default async function Page({ params: { id } }) {
  const user = await getUser(id)
  // ...
}

위 예시에서 비록 2번의 getuser() 함수 호출이 있었지만, 데이터베이스에는 한번의 쿼리만 요청될 것이다. getUser() 함수가 cache() 로 감싸져 있어서 두번째 요청이 첫 요청의 결과를 재사용할 수 있기 때문이다.

단, 앞서 살펴 보았듯 fetch() 는 요청을 자동으로 캐싱하기 때문에 따로 cache() 로 감쌀 필요가 없다. 또 이 모델에서는 여러 컴포넌트에서 동일한 데이터를 여러번 요청하는 한이 있더라도 props로 데이터를 전달하기 보다는 데이터를 실제 사용하는 컴포넌트에서 데이터를 직접 fetch하는 것을 권장한다. 마지막으로 서버 데이터 fetching 함수가 클라이언트에서 절대 사용되지 않도록 하기 위해 server-only package를 사용할 것이 권장된다.

POST requests and cache()

POST 요청이 POST Route Handler 안에 있거나 headers() / cookies() 를 읽은 뒤 발생하지 않는 이상 fetch 를 사용한 요청은 자동으로 중복 처리 된다.
예를 들어 만약 GraphQL을 사용하고 위의 예외상황 POST 요청을 하게 된다면 중복 처리를 위해 cache 를 활용할 수 있다. cache 의 인자로는 반드시 원시 타입만을 포함해야 한다. 깊은 객체는 중복 처리 적용이 되지 않는다.

// utils/getUser.js
import { cache } from 'react'
 
export const getUser = cache(async (id) => {
  const res = await fetch('...', { method: 'POST', body: '...' })
  // ...
})

Preload pattern with cache()

Next.js는 데이터 fetching을 하는 기능이나 컴포넌트에서 선택적으로 preload() export를 보여주는 것을 패턴으로 제안한다.

// components/User.js
import { getUser } from '@utils/getUser'
 
export const preload = (id) => {
  // void 는 주어진 표현식을 평가하고 undefined를 반환한다.
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
  void getUser(id)
}
export default async function User({ id }) {
  const result = await getUser(id)
  // ...
}

preload 를 호출함으로써 데이터가 필요할 것 같은 상황(즉 조금 앞서서)에 데이터 fetching을 시작할 수 있다.

// app/user/[id]/page.js
import User, { preload } from '@components/User'

export default async function Page({ params: { id } }) {
  preload(id) // user 데이터 로딩을 시작한다.
  const condition = await fetchCondition()
  return condition ? <User id={id} /> : null
}

이 때 preload() 함수는 어떤 이름을 가져도 상관없다. API가 아닌 패턴이기 때문이다. 이 패턴은 선택적으로 사용하면 되고 때에 따라 최적화를 위해 사용할 수 있다. 병렬적인 데이터 fetching에서 더 나아간 최적화 방식이다. 이제 프로미스를 props로 내려줄 필요가 없고 preload 패턴에 의존할 수 있다.

Combining cache , preload , and server-only

제목의 세가지를 조합하여 앱 전반에서 사용가능한 데이터 fetching 기능을 만들 수 있다.

// utils/getUser.js
import { cache } from 'react'
import 'server-only'
 
export const preload = (id) => {
  void getUser(id)
}
 
export const getUser = cache(async (id) => {
  // ...
})

이 접근 방식으로 데이터를 fetch하고 응답을 캐싱하며 데이터 fetching이 서버에서만 발생하는 것을 보장할 수 있다.

getUser.js 는 레이아웃, 페이지, 컴포넌트에서 사용되어 유저 데이터가 fetch 되었을 때 그 통제권을 줄 수 있다.

Segment-level Caching

참고 : 향상된 개별화와 캐싱에 대한 통제를 위해 각 요청마다 캐싱방식을 정하는 것을 추천한다.

Segment-level 캐싱은 route segment 내에서 사용되는 데이터를 캐싱하고 갱신하도록 해준다.

이 매커니즘은 경로의 다양한 segment들이 전체 route의 캐시 생애주기를 통제할 수 있도록 한다. route 계층 내의 page.js , layout.jsrevalidate 값을 export 하여 route의 갱신 시간을 설정할 수 있다.

// app/page.js
export const revalidate = 60 // 이 segment를 매 60초마다 갱신한다.

만약 페이지, 레이아웃, 컴포넌트 내의 fetch 요청이 각각 revalidate 빈도를 특정한다면 그 중 가낭 작은 값이 사용된다.
fetchCache'only-cache' 또는 'force-cache' 로 설정하여 모든 fetch 요청이 캐싱을 하도록 보장할 수 있지만 개별 fetch 요청에 의해 갱신 빈도는 낮아질 수 있다.

profile
개발자를 지망하는 경영학도

0개의 댓글