데이터 캐싱
Next.js는 요청별(권장) 또는 전체 route 세그먼트에 대한 데이터 캐싱을 기본적으로 지원합니다.
요청별 캐싱
fetch()
기본적으로 모든 fetch() 요청은 캐시되고 자동으로 중복 제거됩니다. 즉, 동일한 요청을 두 번 수행하면 두 번째 요청은 첫 번째 요청의 결과를 재사용합니다.
// app/page.tsx
async function getComments() {
const res = await fetch('https://...'); // The result is cached
return res.json();
}
// 이 함수는 두 번 호출되지만 결과는 한 번만 가져옵니다.
const comments = await getComments(); // cache MISS
// 두 번째 호출은 애플리케이션 내 어디든 가능합니다.
const comments = await getComments(); // cache HIT
다음과 같은 경우 요청이 캐시되지 않습니다:
- 동적 메서드(next/header, export const POST 또는 비슷한것들)가 사용되었고 fetch가 POST 요청인 경우(또는 Authorization 부여 또는 cookie 헤더 사용)
- fetchCache는 기본적으로 캐시를 건너뛰도록 구성됩니다.
- revalidate: 0 또는 cache: 'no-store'이 개별 fetch에 구성됩니다.
fetch를 사용하는 요청은 revalidate 옵션을 지정하여 요청의 재검증 빈도를 제어할 수 있습니다.
export default async function Page() {
// revalidate this data every 10 seconds at most
const res = await fetch('https://...', { next: { revalidate: 10 } });
const data = res.json();
// ...
}
React cache()
React에서는 cache()를 사용하여 요청을 중복 제거하고 래핑된 함수 호출의 결과를 메모리에 저장할 수 있습니다. 동일한 인자로 호출된 동일한 함수는 함수를 다시 실행하는 대신 캐시된 값을 재사용합니다.
// utils/getUser.ts
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const user = await db.user.findUnique({ id });
return user;
});
// app/user/[id]/layout.tsx
import { getUser } from '@utils/getUser';
export default async function UserLayout({ params: { id } }) {
const user = await getUser(id);
// ...
}
// app/user/[id]/page.tsx
import { getUser } from '@utils/getUser';
export default async function Page({
params: { id },
}: {
params: { id: string };
}) {
const user = await getUser(id);
// ...
}
위의 예제에서 getUser() 함수는 두 번 호출되지만 데이터베이스에 대한 쿼리는 한 번만 수행됩니다. 이는 getUser()가 cache()로 래핑되어 두 번째 요청에서 첫 번째 요청의 결과를 재사용할 수 있기 때문입니다.
알아두면 좋습니다:
fetch()는 요청을 자동으로 캐시하므로 fetch()를 사용하는 함수를 cache()로 래핑할 필요가 없습니다. 자세한 내용은 자동 요청 중복 제거를 참조하세요.
이 새로운 모델에서는 여러 컴포넌트에서 동일한 데이터를 요청하는 경우에도 컴포넌트 간에 데이터를 prop으로 전달하지 말고 필요한 컴포넌트에서 직접 데이터를 가져오는 것이 좋습니다.
서버 데이터 불러오기 함수가 클라이언트에서 사용되지 않도록 서버 전용 패키지를 사용하는 것이 좋습니다.
GraphQL 및 cache()
POST 요청은 fetch를 사용할 때 자동으로 중복 제거됩니다. 단, POST 경로 핸들러 내부에 있거나 headers()/cookies()를 읽은 후에 오는 요청은 예외입니다. 위와 같은 경우에 GraphQL과 POST 요청을 사용하는 경우 캐시를 사용하여 요청을 중복 제거할 수 있습니다. cache 인수는 플랫해야 하며 기본 요소만 포함해야 합니다. 딥 객체는 중복 제거를 위해 일치하지 않습니다.
// utils/getUser.ts
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const res = await fetch('/graphql', { method: 'POST', body: '...' });
// ...
});
cache()를 사용한 프리로드 패턴
패턴으로서, 데이터 불러오기를 수행하는 유틸리티 또는 컴포넌트에서 선택적으로 preload() 내보내기를 노출하는 것이 좋습니다.
// components/User.tsx
import { getUser } from '@utils/getUser';
export const preload = (id: string) => {
// void는 주어진 식을 평가하고 정의되지 않은 값을 반환합니다.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
void getUser(id);
};
export default async function User({ id }: { id: string }) {
const result = await getUser(id);
// ...
}
preload()를 호출하면 필요한 데이터를 바로 가져올 수 있습니다.
// app/user/[id]/page.tsx
import User, { preload } from '@components/User';
export default async function Page({
params: { id },
}: {
params: { id: string };
}) {
preload(id); // starting loading the user data now
const condition = await fetchCondition();
return condition ? <User id={id} /> : null;
}
알아두면 좋습니다:
preload() 함수는 어떤 이름이라도 사용할 수 있습니다. API가 아니라 패턴입니다.
이 패턴은 완전히 선택 사항이며 사례별로 최적화하는 데 사용할 수 있습니다. 이 패턴은 병렬 데이터 불러오기에 대한 추가 최적화입니다. 이제 프로미스를 prop으로 전달할 필요가 없으며 대신 프리로드 패턴을 사용할 수 있습니다.
캐시, 프리로드, 서버 전용 결합하기
캐시 기능, 프리로드 패턴, 서버 전용 패키지를 결합하여 앱 전체에서 사용할 수 있는 데이터 가져오기 유틸리티를 만들 수 있습니다.
import { cache } from 'react';
import 'server-only';
export const preload = (id: string) => {
void getUser(id);
};
export const getUser = cache(async (id: string) => {
// ...
});
이 접근 방식을 사용하면 데이터를 열심히 가져오고, 응답을 캐시하고, 이 데이터 가져오기가 서버에서만 발생하도록 보장할 수 있습니다.
레이아웃, 페이지 또는 컴포넌트에서 getUser.ts 내보내기를 사용하여 사용자 데이터를 가져오는 시기를 제어할 수 있습니다.
세그먼트 수준 캐싱
참고: 캐싱에 대한 세분성 및 제어를 개선하려면 요청별 캐싱을 사용하는 것이 좋습니다.
세그먼트 수준 캐싱을 사용하면 route 세그먼트에서 사용된 데이터를 캐싱하고 재검증할 수 있습니다.
이 메커니즘을 사용하면 route의 여러 세그먼트가 전체 route의 캐시 수명을 제어할 수 있습니다. route 계층 구조의 각 page.tsx 및 layout.tsx는 route의 재검증 시간을 설정하는 revalidate 값을 내보낼 수 있습니다.
// app/page.tsx
export const revalidate = 60; // revalidate this segment every 60 seconds
알아두면 좋습니다:
- 페이지, 레이아웃 및 가져오기 요청이 모두 재검증 빈도를 지정하는 경우 세 가지 중 가장 낮은 값이 사용됩니다.
- 고급: 모든 가져오기 요청이 캐싱을 선택하도록 하되, 개별 가져오기 요청에 따라 재검증 빈도를 낮출 수 있도록 fetchCache를 'only-cache' 또는 'force-cache'로 설정할 수 있습니다. 자세한 내용은 fetchCache를 참조하세요.