1. Fetching
개인화되지 않은 요청
- fetch의 기본 캐시 옵션은 force-cache로 요청의 응답이 원격 서버에 캐싱됩니다
- force-cache는 개인화되지 않고 GET 메서드로 요청할 때에만 사용합니다.
개인화된 요청
- 개인화된 요청인 경우, force-cache를 사용하면 모든 사용자에게 동일한 응답이 반환됩니다.
- API와의 통신 과정에서 개인화된 요청에는 Authorization 헤더를 추가하여 보냅니다.
- Authorization 에는 cookie의 access가 포함됩니다.
- cookies(), headers(), noStore(), searchParams 등의 동적 함수를 사용하면 동적 요청으로 간주되어 동적 렌더링으로 전환됩니다.
- Authorization header or cookie를 사용하면 요청이 캐시 되지 않습니다.
- 개인화된 요청은 cache 옵션을 no-store로 설정해야 합니다. 이렇게 하면 캐시를 조회하지 않고 요청이 있을 때마다 원격 서버에서 리소스를 가져옵니다. 해당 리소스로 캐시를 업데이트하지 않습니다. → 개인화된 요청을 원격 서버에 캐싱하지 않음을 의미합니다.
- GET이 아닌 method (예: POST, PUT, DELETE…) 도 자동으로 캐시 되기 때문에, no-store로 설정합니다.
‼ 기존에는 tanstack-query를 사용하여 client-side에서 백엔드 API 호출 시 사용자의 네트워크 비용이 발생했습니다. 그러나 서버 컴포넌트에서 fetch를 사용한 요청은 프런트 서버 비용이 발생합니다. 캐시 관리를 효율적으로 하지 않으면, 불필요한 프론트 서버 비용과 백엔드 API 호출 비용이 동시에 발생할 수 있습니다.
2. Caching
Data Cache
💡 Next.js 에서는 서버 요청 및 배포 전반에 걸쳐 fetch 요청에 대한 결과를 유지하는 데이터 캐시가 내장되어 있습니다.
이 데이터 캐시는 클라이언트 캐시가 아닌 서버 캐시를 의미합니다.
Duration
fetch의 캐시 옵션을 force-cache로 설정 후 배포하게 되면, revalidate 하거나 opt-out 하지 않는 이상 모든 배포에서 캐싱된 데이터가 유지됩니다.
- Vercel에서 Project 캐시를 삭제하는 방법
Revalidating
revalidate 요청이 들어오면 Vercel 인프라의 게이트웨이에서 서버리스 함수를 호출하여 revalidate 후 새로운 데이터를 반환합니다.
Time-Based
revalidate 주기를 설정하여 API를 백그라운드에서 revalidate 시킬 수 있습니다.
// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })
On-Demand
- revalidateTag를 이용하여 사용자의 동작에 따라 API를 백그라운드에서 revalidate 할 수 있습니다.
fetch(
`https://.../v1/individual_inquiry/?${individualInquiryQueryParams?.toString()}`,
{
next: {
revalidate: 60,
// fetch tags에 api 고유의 키 + query params 조합의 키 추가
tags: [FETCH_TAGS_INDIVIDUAL_INQUIRY_API.LIST,`${
FETCH_TAGS_INDIVIDUAL_INQUIRY_API.LIST
}${individualInquiryQueryParams?.toString()}`],
},
},
);
- FETCH_TAGS_INDIVIDUAL_INQUIRY_API.LIST가 삽입된 모든 API revalidate
revalidateTag(FETCH_TAGS_INDIVIDUAL_INQUIRY_API.LIST)
- FETCH_TAGS_INDIVIDUAL_INQUIRY_API.LIST + query params 가 삽입된 API revalidate
revalidateTag(`${FETCH_TAGS_INDIVIDUAL_INQUIRY_API.LIST
}${individualInquiryQueryParams?.toString()}`)
- revalidatePath를 이용하여 해당 경로의 데이터 캐시와, 경로 캐시를 revalidate 할 수 있습니다.
revalidatePath('/ko');
- route handlers를 생성하고 API 갱신하는 로직을 구현합니다.
- CMS에서 새로운 콘텐츠가 업로드될 때 route API(ex. {FRONT_DOMAIN}/api/revalidate/{revalidateName})를 호출하여 데이터를 갱신합니다.
- 해당 방법은 읽기 요청이 쓰기(갱신 후 data cache set)요청보다 많을 때 api 요청 비용이 효과적으로 절감됩니다.
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
import { ENV } from '@/configs/env';
const API_KEY = ENV['X-API-KEY'];
async function invalidateCache(revalidateName: string, id: string | null) {
if (id) {
revalidateTag(`${revalidateName}${id}`);
revalidateTag(revalidateName);
} else {
revalidateTag(revalidateName);
}
return new Response(JSON.stringify({}), {
status: 200,
statusText: 'success',
});
}
async function invalidRequest() {
return new Response(JSON.stringify({}), {
status: 401,
statusText: 'unauthorized',
});
}
export async function DELETE(
req: NextRequest,
{
params,
}: {
params: {
revalidateName: string;
};
},
) {
const isValidApiKey = req.headers.get('X-API-KEY') === API_KEY;
if (!isValidApiKey) {
return invalidRequest();
}
const { revalidateName } = params;
const searchParams = new URLSearchParams(req.nextUrl.searchParams);
const id = searchParams.get('id');
try {
return invalidateCache(revalidateName, id);
} catch (error) {
return new Response(JSON.stringify({}), {
status: 500,
statusText: 'internal server error',
});
}
}
Opting out
- 요청 단위의 데이터 캐시 해제
// Opt out of caching for an individual `fetch` request
fetch(`https://...`, { cache: 'no-store' })
- 경로 단위의 모든 요청 데이터 캐시 해제
// Opt out of caching for all data requests in the route segment
export const dynamic = 'force-dynamic'
Request Memoization
- fetch를 사용할 때 동일한 url & option의 Request를 자동으로 메모합니다.
- layout page component generateMetadata generateStaticParams에서 같은 요청 시 첫 번째 Request에서 캐싱되고, 후속 Request 시 함수를 실행하지 않고 메모리에서 반환합니다.
- fetch를 사용한 Request Memoization은 GET method에서만 적용됩니다.
// a
<Suspense fallback={...}>
<CelebSection
data={celebList({
query: celebQueryParams,
})}
/>
</Suspense>
// a
<Suspense fallback={...}>
<CelebSection
data={celebList({
query: celebQueryParams,
})}
/>
</Suspense>
// a-1
<Suspense fallback={...}>
<CelebSection
data={celebList({
query: celebQueryParams1,
})}
/>
</Suspense>
Duration
요청 메모는 서버 요청 간에 공유되지 않고 React 컴포넌트 트리 안에서 렌더링 중에만 적용되므로 재검증할 필요가 없습니다.
Opting out
fetch를 사용한 요청에서 아래의 방법으로 요청 메모를 해제할 수 있습니다.
const { signal } = new AbortController()
fetch(url, { signal })
'Next.js' 카테고리의 다른 글
무거워진 Next.js 앱, Micro Frontend Architecture로 가볍게 만들기 (3) | 2025.08.18 |
---|---|
Next.js에서 i18next-parser와 next-intl을 활용한 다국어 처리 자동화 (0) | 2025.04.24 |
Next.js에서 Server Action과 React hooks를 조합해서 사용자 경험 개선하기 (1) | 2024.06.26 |
Next.js에서 React Server Component의 렌더링 방식 알아보기 (0) | 2024.06.25 |
Next.js에서 fetch와 tanstack-query 효율적으로 사용하기 (0) | 2024.06.23 |