next.js는 요청별(권장) 또는 전체 경로 세그먼트 모두에서 데이터 캐싱을 기본적으로 지원한다.
기본적으로, 모든 fetct() 요청은 캐시되고 자동으로 중복제거 된다. 즉, 만약 같은 요청을 두번 생성한다면 두번째 요청은 첫번째 요청의 결과를 재사용 될 것이다.
async function getComments() {
const res = await fetch('https://...'); // The result is cached
return res.json();
}
// This function is called twice, but the result is only fetched once
const comments = await getComments(); // cache MISS
// The second call could be anywhere in your application
const comments = await getComments(); // cache HIT
요청이 캐시되지 않는 경우:
fetch를 사용하여 만든 요청은 요청의 재검증 빈도를 제어하는 옵션을 지정할 수 있다.
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를 사용하면 래핑된 함수 호출의 결과를 메모하면서 캐시하고 중복제거할 수 있다. 동일한 인수로 호출된 동일한 함수는 함수를 다시 실행하는 대신 캐시된 값을 재사용한다.
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const user = await db.user.findUnique({ id });
return user;
});
import { getUser } from '@utils/getUser';
export default async function UserLayout({ params: { id } }) {
const user = await getUser(id);
// ...
}
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()로 래핑할 필요가 없다. 자세한 내용은 automatic request deduping 문서를 참조하라.
- 이 새로운 모델에서는 여러 컴포넌트에서 동일한 데이터를 요청하더라도 구성 요소 간에 데이터를 props로 전달하는 대신 데이터가 필요한 컴포넌트에서 직접 데이터를 fetch하는 것을 권장한다.
- 서버 데이터 fetch 기능이 클라이언트에서 사용되지 않도록 서버 전용 패키지를 사용하는 것을 권장한다.
POST 요청은 POST 경로 핸들러 내부에 있거나 headers()/cookies()를 읽은 후에 오지 않는 한 fetch를 사용할 때 자동으로 중복 제거된다. 예를 들어 위의 경우에 GraphQL 및 POST 요청을 사용하는 경우 캐시를 사용하여 요청을 중복 제거할 수 있다. 캐시 인수는 무계층이어야 하며 원시변수만 포함해야 한다. 심층 개체는 중복 제거와 일치하지 않는다.
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const res = await fetch('...', { method: 'POST', body: '...' });
// ...
});
패턴으로서 데이터를 fetch하는 컴포넌트나 유틸리티 함수에서 preload() export를 선택적으로 노출할 것을 제안한다.
import { getUser } from '@utils/getUser';
export const preload = (id: string) => {
// void evaluates the given expression and returns undefined
// 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를 호출하면 필요할 것 같은 데이터를 적극적으로 가져올 수 있
다.
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가 아니다.
- 이 팬턴은 선택일 뿐이며 사례별로 최적화하는 데 사용할 수 있다. 이 패턴은 병렬 데이터 fetch 위에 추가로 최적화된다. 이제 promise를 prop으로 전달할 필요가 없고 대신 preload 패턴에 의존할 수 있다.
캐시 기능, preload 패턴 및 서버 전용 패키지를 결합하여 앱 전체에서 사용할 수 있는 data fetch 유틸리티를 만들 수 있다.
import { cache } from 'react';
import 'server-only';
export const preload = (id: string) => {
void getUser(id);
};
export const getUser = cache(async (id: string) => {
// ...
});
이 접근 방식을 사용하면 적극적으로 데이터를 가져오고, 응답을 캐시하고, 이 데이터 가져오기가 서버에서만 발생하도록 보장할 수 있다.
getUser.ts 내보내기는 레이아웃, 페이지 또는 구성 요소에서 사용하여 사용자 데이터를 가져오는 시기를 제어할 수 있다.
참고: 캐싱에 대한 세분화 및 제어를 개선하려면 요청당 캐싱을 사용하는 것을 권장한다.
세그먼트 수준 캐싱을 사용하면 경로 세그먼트에 사용된 데이터를 캐시하고 재검증할 수 있다.
이 메커니즘을 사용하면 경로의 여러 세그먼트가 전체 경로의 캐시 수명을 제어할 수 있다. 경로 계층 구조의 각 page.tsx 및 layout.tsx는 경로의 재검증 시간을 설정하는 재검증 값을 내보낼 수 있다.
export const revalidate = 60; // revalidate this segment every 60 seconds
알아두면 좋은 사실.
- 페이지, 레이아웃 및 컴포넌트 내부의 fetch 요청이 모두 재검증 빈도를 지정하는 경우 세 가지 중 가장 낮은 값이 사용됩니다.
- 고급: fetchCache를 'only-cache' 또는 'force-cache'로 설정하여 모든 가져오기 요청이 캐싱을 선택하도록 할 수 있지만 재검증 빈도는 여전히 개별 가져오기 요청에 의해 낮아질 수 있다. 자세한 내용은 fetchCache를 참조하라.
참고
https://nextjs.org/docs/app/building-your-application/data-fetching/caching