[Next.js] 캐싱(Caching)

조아영·2024년 9월 30일

📌

Next.js의 핵심 기능 중 하나는 캐싱(Caching)입니다. Next.js는 대부분 fetch 함수를 사용하여 데이터를 캐싱하며, 이를 통해 성능을 최적화합니다.

Next.js의 fetch는 브라우저의 fetch와 유사하면서도 차이가 있는데요, 그 이유는 Next.js에서 브라우저의 fetch를 확장한 API를 사용하기 때문입니다.

캐싱 방식은 크게 두 가지로 나눌 수 있습니다.

  1. 빌드 시점의 Full Route Cache
  2. 요청 시점의 Data Cache

또한 Request Memoization이라는 기법을 활용하여, 동일한 요청에 대해 중복된 서버 요청을 방지합니다. 개발자는 이러한 기능들을 활용해 최신 데이터를 제공하면서도 빠른 응답을 유지하는 캐싱 전략을 설정할 수 있습니다.

image.png

◼ Full Route Cache (빌드 시점)

Next.js는 빌드 시점에 페이지를 미리 렌더링하여 Full Route Cache에 저장합니다. 이를 통해 서버는 매번 페이지를 새로 렌더링할 필요 없이, 미리 생성된 HTML과 데이터를 제공하여 페이지 로딩 속도를 크게 향상시킬 수 있습니다.

이 과정에서 Next.js는 서버에서 React API를 활용하여 다음과 같은 작업을 수행합니다.

  1. React Server Component Payload 생성: 서버에서 React 컴포넌트를 스트리밍 방식으로 최적화하여 렌더링합니다. 이때 클라이언트에서 사용할 컴포넌트의 정보와 상태가 함께 포함됩니다.
  2. HTML 생성: 이 Payload와 클라이언트 컴포넌트의 JavaScript 코드를 사용하여 서버에서 최종 HTML을 생성합니다.

Full Route Cache는 기본적으로 지속적이며, 동일한 페이지에 대한 요청이 있을 때마다 서버는 빠르게 응답합니다. 만약 데이터가 변경되었거나 실시간 정보가 필요한 경우에는 캐시 무효화 또는 동적 렌더링을 선택할 수 있습니다. revalidate 옵션 등을 통해 이를 제어할 수 있습니다.

◼ Data Cache (요청 시점)

Next.js의 Data Cachefetch 함수를 사용하여 서버 요청 간에도 데이터를 캐싱합니다. 이렇게 함으로써 외부 데이터 소스에 대한 요청 수를 줄이고, 응답 시간을 단축할 수 있습니다.

예를 들어, 다음과 같이 fetch 함수를 사용할 수 있습니다.

const res = await fetch('<https://api.example.com/data>', { next: { revalidate: 3600 } });
const data = await res.json();

위 코드에서 { next: { revalidate: 3600 } }는 1시간(3600초) 동안 캐시된 데이터를 사용하고, 그 이후에는 다시 데이터를 요청하도록 설정합니다. 만약 캐싱 없이 매번 새로운 데이터를 가져오고 싶다면 다음과 같이 설정할 수 있습니다.

const res = await fetch('<https://api.example.com/data>', { cache: 'no-store' });
const data = await res.json();

{ cache: 'no-store' } 옵션을 사용하면 캐시 없이 매번 새로운 데이터를 가져옵니다.

◼ Request Memoization

Request Memoization은 서버 컴포넌트 렌더링 중 동일한 fetch 요청이 중복되더라도, 실제로는 한 번만 실행되게 하는 기능입니다. 이를 통해 중복된 네트워크 요청을 방지하고, 서버 자원응답 시간을 최적화할 수 있습니다.

예를 들어, 여러 컴포넌트에서 동일한 데이터를 호출할 때

async function getCommonData() {
  const res = await fetch('<https://api.example.com/common-data>');
  return res.json();
}

// 컴포넌트 A에서 데이터 호출
const dataA = await getCommonData();

// 컴포넌트 B에서 동일한 데이터 호출
const dataB = await getCommonData();

위 코드에서 getCommonData 함수는 두 번 호출되었지만, 실제 네트워크 요청은 한 번만 발생합니다. 두 번째 호출에서는 첫 번째 호출의 결과를 재사용하게 됩니다. 다만, 메모이제이션은 서버 컴포넌트 렌더링 과정에서만 적용되며, 렌더링이 완료되면 메모리에 저장된 캐시는 제거됩니다.

◼ Next.js의 캐싱 메서드

Next.js 14 버전에서는 fetch 또는 Route Cache를 활용한 다양한 캐싱 방법을 제공합니다.

Next.js는 다양한 캐싱 방법을 제공하여, 데이터와 페이지의 최신 상태를 유지하면서도 응답 속도를 높일 수 있습니다. 각 기능을 적절히 활용하면 애플리케이션의 성능을 크게 향상시킬 수 있습니다.

generateStaticParams

generateStaticParams 함수를 사용하여, 미리 특정 경로에 대한 데이터를 준비할 수 있습니다.

export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json());

  return posts.map((post) => ({
    slug: post.slug,
  }));
}

tags

Next.js에서는 특정 데이터나 페이지를 태그로 식별하고 관리할 수 있습니다. 예를 들어, 다음과 같이 tags 옵션을 설정하여 데이터를 관리할 수 있습니다.
태그를 통해 캐시된 데이터를 효율적으로 무효화할 수 있습니다.

fetch('/api/data', {
  next: {
    revalidate: 60,
    tags: ['product', 'category']
  }
});

revalidate

revalidate캐시된 데이터가 갱신되는 주기를 정의하는 중요한 옵션입니다. 초(second) 단위로 설정되며, 이 시간이 지나면 Next.js는 데이터를 다시 불러와 캐시를 갱신합니다.

fetch('/api/data', {
  next: {
    revalidate: 10
  }
});

revalidateTags

revalidateTags는 특정 태그에 연결된 캐시를 강제로 무효화하고 재검증하는 방법입니다. 데이터 변경 시 이를 통해 캐시를 갱신할 수 있습니다.

import { revalidateTag } from 'next/server';

async function updateProduct(req, res) {
  revalidateTag('product');
  res.status(200).json({ message: 'Product updated and cache revalidated' });
}

0개의 댓글