[Next.js] Caching (Next.js Docs 번역)

bin·2024년 1월 17일
0

기본적으로 Next.js는 성능을 향상하고 비용을 줄이기 위해 가능한 한 많이 캐싱한다. 이는 따로 선택하지 않는 한 정적으로 렌더링되고, 모든 데이터 요청이 캐시됨을 의미한다.

캐싱 동작은 데이터 캐싱 여부와 요청 속성(초기요청 or 후속요청)에 따라 달라진다.

아래 4가지는 Next.js에서 기본적으로 동작하는 캐싱 메커니즘이다.

1. Request Memoization

동일한 데이터를 여러 번 요청할 경우, 최초 요청 때 메모리에 결과가 저장되어 있지 않아 (CACHE MISS 상태) 외부 저장소로부터 결과를 가져오고, 메모리에 저장한다. 이후 요청부터는 함수 실행없이 메모리에 저장된 데이터가 리턴된다. (CACHE HIT 상태)

그러므로 여러 컴포넌트에서 동일한 데이터를 필요로 할 때 루트 컴포넌트에서 함수를 호출하여 필요한 컴포넌트들에게 일일이 전달할 필요없이 필요한 컴포넌트들에서 즉시 호출하여 사용해도 성능 저하를 일으키지 않는다.

async function getItem() {
  // The `fetch` function is automatically memoized and the result
  // is cached
  const res = await fetch('https://.../item/1')
  return res.json()
}
 
// This function is called twice, but only executed the first time
const item = await getItem() // cache MISS
 
// The second call could be anywhere in your route
const item = await getItem() // cache HIT

Revalidating

메모이제이션은 서버 요청 간에 공유되지 않고, 오직 렌더링 중에만 적용되므로 재검증할 필요가 없다.

Request Memoization 선택 해제

const { signal } = new AbortController()
fetch(url, { signal })

2. Data Cache

Next.js는 들어오는 서버 요청과 배포 전반에 걸친 데이터 fetch의 결과를 유지하는 Data Cache가 내장되어 있다. 기본적으로 fetch를 사용하는 데이터 요청은 캐싱된다.

  • 최초 fetch 요청 시 Next.js는 Data Cache에서 캐싱된 응답을 확인한다.
  • 만약 캐싱된 응답이 있다면 이를 즉시 리턴하고 기억한다.
  • 만약 캐싱된 응답이 없다면 데이터 저장소에 요청하여 그 결과를 Data Cache에 저장한 후 기억한다.
  • 캐시되지 않은 데이터의 경우, 그 결과는 항상 데이터 저장소에서 가져와서 기억한다.

Revalidating

  • Time-based Revalidation: 일정 시간이 지난 후, 새로운 요청이 발생한 후 데이터를 Revalidating한다. 이는 데이터가 빈번하게 변경되지 않고, 최신성이 중요하지 않은 데이터에 유효하다.

🤔 Request Memoization과 Data Cache, 비슷해보이는데 뭐가 다른걸까?

Data Cache는 들어오는 요청과 배포 전반에 걸쳐 지속되지만, Request Memoization은 오직 요청의 생명주기 동안에만 지속된다.

// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })
  • On-demand Revalidation

Data Cache 선택 해제

개별 데이터 가져오기 시 cache option을 no-store로 설정함으로써 캐싱 기능을 사용하지 않을 수 있다.

// Opt out of caching for an individual `fetch` request
fetch(`https://...`, { cache: 'no-store' })

3. Full Route Cache

next.js는 빌드 시 자동으로 경로를 렌더링하고 캐시한다. 이는 모든 요청에 대해 서버에 렌더링하는 대신 캐시된 경로를 제공함으로써 더 빠른 페이지 로딩을 할 수 있는 최적화다.

Full Route Cache의 작동 방법을 이해하기 위해서는 React가 렌더링을 다루는 방법과 Next.js가 결과를 캐싱하는 방법을 살펴보는 것이 도움이 된다.

1. React Rendering on the Server

서버에서 Next.js는 렌더링을 조정하기 위해 React의 API를 사용한다. 해당 렌더링 작업은 개별 Route 세그먼트와 Suspense 바운더리별로 여러 단위로 분할된다. 나눠진 단위들은 두 단계로 렌더링 된다.

    1. React는 서버 컴포넌트를 React Server Component Payload라 불리는 스트리밍에 최적화된 특별한 데이터 포맷으로 렌더링한다.
    1. Next.js는 서버에서 HTML을 렌더링 하기 위해 React Server Component Payload와 클라이언트 컴포넌트 자바스크립트 지침을 사용한다.

이는 작업을 캐싱하거나 응답을 보내기 전, 모든 것이 렌더링될 때까지 기다릴 필요가 없음을 뜻한다. 대신 작업이 완료되면 응답을 스트리밍할 수 있다.

React Server Component Payload란?

렌더링된 React Server Components tree의 작은 바이너리 표현이다. 이것은 클라이언트 React에서 브라우저 돔을 업데이트 하기위해 사용된다.
React Server Component Payload는 다음 요소들을 포함한다.

  • 서버 컴포넌트의 렌더링 결과
  • 클라이언트 컴포넌트를 렌더링해야 하는 Placeholders 및 해당 자바스크립트 파일에 대한 참조
  • 서버 컴포넌트에서 클라이언트 컴포넌트로 전달되는 모든 props

2. Next.js Caching on the Server (Full Route Cache)

Next.js의 기본 동작은 서버에 있는 라우트의 렌더링된 결과(React Server Component Payload과 HTML)를 캐시하는 것이다. 이것은 빌드 시 또는 재검증하는 동안 정적으로 렌더링된 라우트를 적용한다.

3. React Hydration and Reconciliation on the Client

요청 시, 클라이언트에서는

    1. HTML은 빠르고 비상호작용적인 초기 클라이언트와 서버 컴포넌트의 preview를 즉시 보여주기 위해 사용된다.
    1. React Server Components Payload는 클라이언트와 렌더링된 서버 컴포넌트 트리를 조정하고, DOM을 업데이트 하는 데 사용된다.
    1. 자바스크립트 지침은 클라이언트 컴포넌트를 동적화하고, 어플리케이션을 상호작용하는 방식으로 만드는 데 사용된다.

4. Next.js Caching on the Client (Router Cache)

React Server Component Payload는 개별 라우트 세그먼트로 분할된 별도의 메모리 내 캐시인 클라이언트 측 Router Cache에 저장된다. 이 Router Cache는 이전에 방문한 경로를 저장하고 향후 경로를 미리 가져옴으로써 탐색 경험을 개선하는 데 사용된다.

5. Subsequent Navigations

후속 탐색 시 또는 미리 가져오는 동안에, Next.js는 React Server Components Payload가 Router Cache에 저장 되었는 지를 체크할 것이다. 만약 그러하다면 새로운 요청을 서버에 보내는 것을 건너뛸 것이다.

만약 라우트 세그먼트가 캐시에 없다면, Next.js는 서버로부터 React Server Components Payload를 가져올 것이고, 클라이언트의 Router Cache를 채울 것이다.

Static and Dynamic Rendering

빌드 시 라우트 캐싱 여부는 라우트가 정적으로 렌더링되는지 동적으로 렌더링되는지에 따라 달라진다. 정적 라우트는 기본적으로 캐시되는 반면, 동적 라우트는 요청 시 렌더링 되고, 캐시되지 않는다.

Duration

기본적으로 Full Route Cache는 지속적이다. 이는 렌더링 결과가 사용자 요청 전반에 걸쳐 캐싱됨을 의미한다.

Invalidation

Full Route Cache을 무효화하는 방법은 2가지다.

  • Revalidating Data: Data Cache Revalidating은 서버의 컴포넌트를 리렌더링하고 새로운 렌더링 결과를 캐싱함으로써 Router Cache를 무효화한다.
  • Redeploying: 배포 전반에 걸쳐 지속되는 Data Cache와 다르게, Full Route Cache는 새 배포에서 지워진다.

Full Route Cache 선택 해제

다음을 통해 Full Route Cache를 선택 해제 할 수 있다. 즉, 들어오는 모든 요청에 대해 컴포넌트를 동적으로 렌더링할 수 있다.

  • Using a Dynamic Function: 이 방법으로 Full Route Cache에서 라우트를 선택 해제하고, 요청 시 동적으로 렌더링힐 수 있다. Data Cache는 여전히 사용할 수 있다.

  • Using the dynamic = 'force-dynamic' or revalidate = 0 route segment config options: 이 방법으로 Full Route Cache와 Data Cache를 건너뛸 수 있다. 즉, 서버로 들어오는 요청마다 컴포넌트가 렌더링되고 데이터를 가져온다. Router Cache는 클라이언트 측 캐시이므로 여전히 적용된다.

  • Opting out of the Data Cache: 라우트에 캐시되지 않은 fetch 요청이 있는 경우, Full Route Cache에서 라우트가 선택 해제될 것이다. 특정 fetch 요청에 대한 데이터는 들어오는 모든 요청에 대해 가져와질 것이다. 캐싱을 선택 해제하지 않은 다른 fetch 요청은 여전히 데이터가 캐싱될 것이다. 이는 캐시된 데이터와 캐시되지 않은 데이터의 혼합을 가능토록 한다.

4. Router Cache

0개의 댓글