개요
캐싱
캐싱은 서버가 동일한 요청에 대해 매번 데이터베이스나 외부 API에 접근하지 않고, 미리 저장된 데이터를 빠르게 반환함으로써 응답 시간을 단축하고, 서버의 부하를 줄이는 기술이다.
동일한 콘텐츠를 보여주는데 api를 매번 새롭게 요청 할 경우 네트워크 부하 및 성능 저하가 발생한다.
그래서 매번 최신 데이터가 필요하거나 데이터 갱신이 자주 이뤄져야하는게 아닌경우 캐싱을 활용해 api 요청방식의 전략을 잘 짜는 것이 중요하다.
나의 경우 주가 그래프 파트 및 일부 요소에만 적용을 했는데 Next JS에서 제공하는 캐싱 개념과 우리 서비스에 적합한 캐싱 방법을 사용해보려 한다.
Next.js Caching (latest ver.)
Next.js에선 여러가지 캐싱 방식을 제공하고, 각 캐싱 방식들은 다른 목적, 시기, 실행 위치가 다르다.
Next.js에서는 성능 향상과 비용 절감을 목적으로 선택 해제하지 않는 이상 가능한 많이 캐싱한다.
아래 사진은 빌드 시 그리고, 정적 경로가 처음 방문될 때의 동작을 보여준다.
캐싱 동작은 경로가 (정적,동적) 렌더링 방식과 데이터의 캐싱 여부와 초기 방문 혹은 후속 네비게이션에 따른 요청인지에 따라 달라진다.

Request Memoization
Next.js 에선 동일한 URL과 옵션을 가진 요청들을 자동으로 메모이제이션 하도록 fetch API를 확장한다.
이는 여러 경로에서 동일한 데이터를 가져오기위해 fetch 함수를 호출해도 한 번만 실행되는 것을 의미한다.

예시코드를 보면 fetch를 두 번 실행하나 정작 두 번째 호출에선 캐싱된 데이터를 가져오는 셈이다.
async function getPosts() {
// `fetch` 함수는 자동으로 메모이제이션되고 결과는 캐싱된다.
const res = await fetch(`${process.env.NEXT_PUBLIC_LOCAL}/api/main/dailyPost`)
return res.json()
}
// 이 함수는 두 번 호출되지만 처음 한 번만 실행된다.
const item = await getPosts() // cache MISS
const item = await getPosts() // cache HIT
Request Memoization 작동방식은 다음과 같다. 해당 방식은 React 기능이다.

추가로 메모이제이션은 fetch의 GET메서드에만 적용되고 서버 컴포넌트의 fetch 요청에 적용된다.
특정요청이 처음 호출 시엔 해당 결과가 메모리에 없기에 캐시 MISS가 발생한다.
즉 함수실행에 따라 데이터가 외부, (백엔드 서버 DB)로 부터 가져와지며 결과가 메모리에 저장된다.
다음 후속 호출에선 캐시 HIT가 발생해 함수 실행 없이 메모리에서 데이터를 반환한다.
경로 렌더링(HTML반환)이 완료되고 렌더링 패스(서버에서 렌더링을 마치고 응답을 완료)가 끝나면 메모리가 리셋되고 모든 요청 메모이제이션 항목은 지워진다.
즉 Request Memoization에서의 캐시는 서버 요청의 수명 동안 지속 되며, React 컴포넌트 트리 렌더링이 완료될 때까지 유지된다.
Data Cache
Next.js에는 서버 요청과 배포간의 데이터 fetch를 지속적으로 유지하는 내장 데이터 캐시가 있다.
이는 Next.js에서 네이티브 fetch API를 확장해 서버에서 각 요청이 자체 지속적 캐싱을 설정하도록 하기에 동작한다.
추가로 브라우저의 fetch의 cache 옵션의 경우 HTTP 캐시와 상호작용 하는 방식을 의미한다.
fetch('/api/data', {
method: 'GET',
cache: 'no-store',
});
반면 Next.js fetch의 cache 옵션은 서버의 데이터 캐시와 상호작용 하는 방식을 의미한다.
export async function GET(req) {
const data = await fetch('https://api.example.com', { cache: 'no-store' });
return new Response(JSON.stringify(data), {
headers: { 'Cache-Control': 'no-store' },
});
}
Data Cache의 작동 방식은 다음과 같다.

- force-cache 옵션이 있는 fetch 요청이 렌더링 중 처음 호출 시 Next.js의 데이터 캐시에서 캐시된 응답을 확인한다.
- 캐시 응답이 발견 시 즉시 반환되고 메모이제이션 된다.
- 상단의 Request Memoization을 의미한다.
- 캐시된 응답이 없을 시, 요청이 데이터 소스로 전송되고 결과가 데이터 캐시에 저장되며 메모이제이션 된다.
- cache 옵션이 없거나 no-store 을 사용하는 경우 결과는 항상 데이터 소스에서 가져오고 메모이제이션 된다.
- 데이터 캐싱의 여부와 별개로 요청은 항상 메모이제이션 되고, React 렌더링 패스 동안 동일한 데이터에 대해 중복 요청을 하지 않는다.
Data Cache와 Request Memoization의 차이
Data Cache의 경우 다른 유저의 요청 간에도 캐시를 유지하기에 서버 전체에서 배포간에 지속되며 재검증하거나 선택 해제 하지 않는 한 유지된다.
Request Memoization은 위에서 말한대로 하나의 요청의 수명 동안만 지속되기에 렌더링 서버가 네트워크 요청을 중복을 보내지 못하도록 막는 것을 목표로한다.
Revalidating
캐시된 데이터는 두 가지 방법으로 재검증이 가능하다.
1. 시간 기반 재검증 : 일정 시간이 경과한 후 새 요청이 들어오면 데이터를 재검증함.
// 최대 한 시간마다 재검증
fetch('https://...', { next: { revalidate: 3600 } })
- 시간이 지난 후 다음 요청은 여전히 캐시된 데이터를 반환하지만, 백그라운드에서 데이터 재검증을 트리거해 성공적으로 가져와지면 최신 데이터로 Data Cache를 업데이트 한다.
- 재검증 실패 시 이전 데이터가 변경되지 않고 유지된다.
2. 온디맨드 재검증 : 이벤트에 따라 데이터를 재검증 한다. (경로, 캐시태그에 따라)
첫 번째 fetch요청이 호출되면 데이터가 외부 데이터 소스에서 가져와져 Data Cache에 저장된다.
온디맨드 재검증이 트리거 될 시 캐시항목이 캐시에서 제거되고, 다음 요청이 있을 때 다시 캐시 MISS가 되며 외부 데이터 소스에서 가져와 Data Cache에 저장된다.
기본적으로 fetch 요청은 캐싱되지 않는다. 따라서 매 fetch 호출마다 데이터가 데이터 소스에서 가져와진다.
CMS
Next 공식문서를 보면서 CMS를 언급하며 설명할 때가 종종 있는데 CMS를 처음 들어 찾아 보았다.
CMS는 웹사이트, 애플리케이션에서 콘텐츠를 관리하기 위한 도구로 이를 통해 관리자 대시보드에서 텍스트 이미지 같은 콘텐츠들을 수정이 가능하다.
Next.js에서 언급되는 이유로는 Next.js는 CMS 데이터를 가져와 페이지를 정적 생성하거나 서버에서 SSR하는 등 잘 통합되도록 기능을 제공하기에 여러 활용 방안에 대하여 설명 하는 듯 하다.
그 예시로는 콘텐츠만 관리하고, UI는 개발자가 구현하도록 API를 제공하는 Headless CMS와 모두 제공하는 Traditional CMS가 있다고한다.
다음 코드와 같이 CMS의 데이터를 가져와 페이지를 생성하고, CMS에서 게시글이 업데이트 된다면, 위에서 설명한 On-demand Revalidation을 통해 실시간 변경 사항을 반영할 수 있다.
// 데이터 페칭 예시 (Headless CMS - Contentful)
import { createClient } from 'contentful';
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});
export async function getStaticProps() {
const entries = await client.getEntries({ content_type: 'blogPost' });
return {
props: {
posts: entries.items,
},
};
}
추가로 Full Route Cache, Client-side Router Cache에 대한 내용이 더 있지만, 당장 내가 SSR을 통해 fetch하는데에서 데이터 캐싱을 하는게 목표였기에 이후 내용은 FLEX 카테고리가 아닌 Next 파트에서 공부 후 작성해보려한다.
내 코드 적용
몰랐는데 Next.js(13버전 이후)에선 route handler의 경우 기본적으로 캐싱을 하기에, 오히려 캐싱을 사용하지 않으려면 no-store을 지정해 주어야한다.
처음은 서버컴포넌트로 api 폴더의 route.ts파일의 api를 요청하는 함수인 callGet
import { MAIN_CONTENTS_TITLE } from '@/app/constants/main';
import { callGet } from '@/app/utils/callApi';
import NoneContent from '../NoneContent';
import DailyPost from './DailyPost';
const DailyPostContainer = async () => {
//callGet은 get메서드를 사용하는 fetch 공동함수
const response = await callGet(
`${process.env.NEXT_PUBLIC_LOCAL}/api/main/dailyPost`,
);
const postData: LandingPostTypes[] = response.isSuccess
? response.result
: [];
export default DailyPostContainer;
import { getLandingLatest } from '@/app/service/getRequest';
import { NextResponse } from 'next/server';
// route.ts에선 체크를 위해 캐시 no-store을 지정해주었다.
export async function GET(req: Request) {
const data = await getLandingLatest(req);
console.log('요청됨');
const response = NextResponse.json(data, {
headers: {
'Cache-Control': 'no-store, max-age=0',
},
});
return response;
}
응답 데이터가 캐싱되었다면, 처음 요청에만 콘솔문이 실행되고 이후 요청에선 콘솔문이 찍혀선 안된다.
현재의 경우 캐시 no-store이기에 매번 새롭게 받아온다. 따라서 새로고침을 할 때마다 새롭게 받아와 콘솔이 매번 찍힌다.
이번엔 no-store삭제 후 요청을 해보겠다.


네트워크 탭에서 보이는 것처럼 서버 컴포넌트에서 가져오는 이미지의 경우 캐싱응답을 의미하는 304상태로 응답이 오는 것을 볼 수 있고..

다음과 같이 test page 경로에 대한 요청(route)은 가지만, 컴포넌트에서 요청하는
api 요청을 의미하는 콘솔은 한 번만 찍히는 걸 볼 수 있다.
결론
따라서 토큰이 사용되거나 개개인 마다 다른 내용이 필요한 경우, 새로운 정보가 매번 와야하는 경우엔 no-store을 사용해서 캐싱을 사용하지 않는게 좋을 것 같다.
캐싱을 공부하기 전엔 캐싱은 무조건 효율적이고 성능에 도움이 될 거라 생각했지만, 가장 먼저 캐싱은 꽤나 복잡한 개념으로 이루어져있다.
그리고 캐싱이 네트워크 성능면에선 좋을지 몰라도, 내가 의도한대로 안 흘러갈 수 있기에 적절한 캐싱전략을 사용해야 한다는 것을 깨달았다.
마지막으로, 정말 라이브러리 버전이 너무 빨리 바뀌고 버전마다 기본 값이나 작동 원리가 달라지는 경우가 빈번해 주기적인 학습과 기록의 중요함을 깨달았다.
본 글은 공식문서를 보고 이해 한 후, 공식문서의 내용과 내가 이해한 바를 합쳐서 필사한 글에 가깝다.

https://nextjs.org/docs/app/building-your-application/caching
Building Your Application: Caching | Next.js
An overview of caching mechanisms in Next.js.
nextjs.org
'코딩 > Flex프로젝트' 카테고리의 다른 글
재테크 블로그 프로젝트 (11) - 리팩토링 [getStaticProps, data fetching] (2) | 2025.01.03 |
---|---|
재테크 블로그 프로젝트 (10) - 리팩토링 [폰트 감소 번들사이즈 최종] (0) | 2025.01.02 |
재테크 블로그 프로젝트 (9) - 리팩토링 [번들 사이즈 줄이기] (0) | 2024.12.29 |
재테크 블로그 프로젝트 (8) - 리팩토링 [번들 사이즈 측정] (1) | 2024.12.28 |
재테크 블로그 프로젝트 (7) Lighthouse 성능 측정 및 개선 (0) | 2024.11.24 |
개요
캐싱
캐싱은 서버가 동일한 요청에 대해 매번 데이터베이스나 외부 API에 접근하지 않고, 미리 저장된 데이터를 빠르게 반환함으로써 응답 시간을 단축하고, 서버의 부하를 줄이는 기술이다.
동일한 콘텐츠를 보여주는데 api를 매번 새롭게 요청 할 경우 네트워크 부하 및 성능 저하가 발생한다.
그래서 매번 최신 데이터가 필요하거나 데이터 갱신이 자주 이뤄져야하는게 아닌경우 캐싱을 활용해 api 요청방식의 전략을 잘 짜는 것이 중요하다.
나의 경우 주가 그래프 파트 및 일부 요소에만 적용을 했는데 Next JS에서 제공하는 캐싱 개념과 우리 서비스에 적합한 캐싱 방법을 사용해보려 한다.
Next.js Caching (latest ver.)
Next.js에선 여러가지 캐싱 방식을 제공하고, 각 캐싱 방식들은 다른 목적, 시기, 실행 위치가 다르다.
Next.js에서는 성능 향상과 비용 절감을 목적으로 선택 해제하지 않는 이상 가능한 많이 캐싱한다.
아래 사진은 빌드 시 그리고, 정적 경로가 처음 방문될 때의 동작을 보여준다.
캐싱 동작은 경로가 (정적,동적) 렌더링 방식과 데이터의 캐싱 여부와 초기 방문 혹은 후속 네비게이션에 따른 요청인지에 따라 달라진다.

Request Memoization
Next.js 에선 동일한 URL과 옵션을 가진 요청들을 자동으로 메모이제이션 하도록 fetch API를 확장한다.
이는 여러 경로에서 동일한 데이터를 가져오기위해 fetch 함수를 호출해도 한 번만 실행되는 것을 의미한다.

예시코드를 보면 fetch를 두 번 실행하나 정작 두 번째 호출에선 캐싱된 데이터를 가져오는 셈이다.
async function getPosts() {
// `fetch` 함수는 자동으로 메모이제이션되고 결과는 캐싱된다.
const res = await fetch(`${process.env.NEXT_PUBLIC_LOCAL}/api/main/dailyPost`)
return res.json()
}
// 이 함수는 두 번 호출되지만 처음 한 번만 실행된다.
const item = await getPosts() // cache MISS
const item = await getPosts() // cache HIT
Request Memoization 작동방식은 다음과 같다. 해당 방식은 React 기능이다.

추가로 메모이제이션은 fetch의 GET메서드에만 적용되고 서버 컴포넌트의 fetch 요청에 적용된다.
특정요청이 처음 호출 시엔 해당 결과가 메모리에 없기에 캐시 MISS가 발생한다.
즉 함수실행에 따라 데이터가 외부, (백엔드 서버 DB)로 부터 가져와지며 결과가 메모리에 저장된다.
다음 후속 호출에선 캐시 HIT가 발생해 함수 실행 없이 메모리에서 데이터를 반환한다.
경로 렌더링(HTML반환)이 완료되고 렌더링 패스(서버에서 렌더링을 마치고 응답을 완료)가 끝나면 메모리가 리셋되고 모든 요청 메모이제이션 항목은 지워진다.
즉 Request Memoization에서의 캐시는 서버 요청의 수명 동안 지속 되며, React 컴포넌트 트리 렌더링이 완료될 때까지 유지된다.
Data Cache
Next.js에는 서버 요청과 배포간의 데이터 fetch를 지속적으로 유지하는 내장 데이터 캐시가 있다.
이는 Next.js에서 네이티브 fetch API를 확장해 서버에서 각 요청이 자체 지속적 캐싱을 설정하도록 하기에 동작한다.
추가로 브라우저의 fetch의 cache 옵션의 경우 HTTP 캐시와 상호작용 하는 방식을 의미한다.
fetch('/api/data', {
method: 'GET',
cache: 'no-store',
});
반면 Next.js fetch의 cache 옵션은 서버의 데이터 캐시와 상호작용 하는 방식을 의미한다.
export async function GET(req) {
const data = await fetch('https://api.example.com', { cache: 'no-store' });
return new Response(JSON.stringify(data), {
headers: { 'Cache-Control': 'no-store' },
});
}
Data Cache의 작동 방식은 다음과 같다.

- force-cache 옵션이 있는 fetch 요청이 렌더링 중 처음 호출 시 Next.js의 데이터 캐시에서 캐시된 응답을 확인한다.
- 캐시 응답이 발견 시 즉시 반환되고 메모이제이션 된다.
- 상단의 Request Memoization을 의미한다.
- 캐시된 응답이 없을 시, 요청이 데이터 소스로 전송되고 결과가 데이터 캐시에 저장되며 메모이제이션 된다.
- cache 옵션이 없거나 no-store 을 사용하는 경우 결과는 항상 데이터 소스에서 가져오고 메모이제이션 된다.
- 데이터 캐싱의 여부와 별개로 요청은 항상 메모이제이션 되고, React 렌더링 패스 동안 동일한 데이터에 대해 중복 요청을 하지 않는다.
Data Cache와 Request Memoization의 차이
Data Cache의 경우 다른 유저의 요청 간에도 캐시를 유지하기에 서버 전체에서 배포간에 지속되며 재검증하거나 선택 해제 하지 않는 한 유지된다.
Request Memoization은 위에서 말한대로 하나의 요청의 수명 동안만 지속되기에 렌더링 서버가 네트워크 요청을 중복을 보내지 못하도록 막는 것을 목표로한다.
Revalidating
캐시된 데이터는 두 가지 방법으로 재검증이 가능하다.
1. 시간 기반 재검증 : 일정 시간이 경과한 후 새 요청이 들어오면 데이터를 재검증함.
// 최대 한 시간마다 재검증
fetch('https://...', { next: { revalidate: 3600 } })
- 시간이 지난 후 다음 요청은 여전히 캐시된 데이터를 반환하지만, 백그라운드에서 데이터 재검증을 트리거해 성공적으로 가져와지면 최신 데이터로 Data Cache를 업데이트 한다.
- 재검증 실패 시 이전 데이터가 변경되지 않고 유지된다.
2. 온디맨드 재검증 : 이벤트에 따라 데이터를 재검증 한다. (경로, 캐시태그에 따라)
첫 번째 fetch요청이 호출되면 데이터가 외부 데이터 소스에서 가져와져 Data Cache에 저장된다.
온디맨드 재검증이 트리거 될 시 캐시항목이 캐시에서 제거되고, 다음 요청이 있을 때 다시 캐시 MISS가 되며 외부 데이터 소스에서 가져와 Data Cache에 저장된다.
기본적으로 fetch 요청은 캐싱되지 않는다. 따라서 매 fetch 호출마다 데이터가 데이터 소스에서 가져와진다.
CMS
Next 공식문서를 보면서 CMS를 언급하며 설명할 때가 종종 있는데 CMS를 처음 들어 찾아 보았다.
CMS는 웹사이트, 애플리케이션에서 콘텐츠를 관리하기 위한 도구로 이를 통해 관리자 대시보드에서 텍스트 이미지 같은 콘텐츠들을 수정이 가능하다.
Next.js에서 언급되는 이유로는 Next.js는 CMS 데이터를 가져와 페이지를 정적 생성하거나 서버에서 SSR하는 등 잘 통합되도록 기능을 제공하기에 여러 활용 방안에 대하여 설명 하는 듯 하다.
그 예시로는 콘텐츠만 관리하고, UI는 개발자가 구현하도록 API를 제공하는 Headless CMS와 모두 제공하는 Traditional CMS가 있다고한다.
다음 코드와 같이 CMS의 데이터를 가져와 페이지를 생성하고, CMS에서 게시글이 업데이트 된다면, 위에서 설명한 On-demand Revalidation을 통해 실시간 변경 사항을 반영할 수 있다.
// 데이터 페칭 예시 (Headless CMS - Contentful)
import { createClient } from 'contentful';
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});
export async function getStaticProps() {
const entries = await client.getEntries({ content_type: 'blogPost' });
return {
props: {
posts: entries.items,
},
};
}
추가로 Full Route Cache, Client-side Router Cache에 대한 내용이 더 있지만, 당장 내가 SSR을 통해 fetch하는데에서 데이터 캐싱을 하는게 목표였기에 이후 내용은 FLEX 카테고리가 아닌 Next 파트에서 공부 후 작성해보려한다.
내 코드 적용
몰랐는데 Next.js(13버전 이후)에선 route handler의 경우 기본적으로 캐싱을 하기에, 오히려 캐싱을 사용하지 않으려면 no-store을 지정해 주어야한다.
처음은 서버컴포넌트로 api 폴더의 route.ts파일의 api를 요청하는 함수인 callGet
import { MAIN_CONTENTS_TITLE } from '@/app/constants/main';
import { callGet } from '@/app/utils/callApi';
import NoneContent from '../NoneContent';
import DailyPost from './DailyPost';
const DailyPostContainer = async () => {
//callGet은 get메서드를 사용하는 fetch 공동함수
const response = await callGet(
`${process.env.NEXT_PUBLIC_LOCAL}/api/main/dailyPost`,
);
const postData: LandingPostTypes[] = response.isSuccess
? response.result
: [];
export default DailyPostContainer;
import { getLandingLatest } from '@/app/service/getRequest';
import { NextResponse } from 'next/server';
// route.ts에선 체크를 위해 캐시 no-store을 지정해주었다.
export async function GET(req: Request) {
const data = await getLandingLatest(req);
console.log('요청됨');
const response = NextResponse.json(data, {
headers: {
'Cache-Control': 'no-store, max-age=0',
},
});
return response;
}
응답 데이터가 캐싱되었다면, 처음 요청에만 콘솔문이 실행되고 이후 요청에선 콘솔문이 찍혀선 안된다.
현재의 경우 캐시 no-store이기에 매번 새롭게 받아온다. 따라서 새로고침을 할 때마다 새롭게 받아와 콘솔이 매번 찍힌다.
이번엔 no-store삭제 후 요청을 해보겠다.


네트워크 탭에서 보이는 것처럼 서버 컴포넌트에서 가져오는 이미지의 경우 캐싱응답을 의미하는 304상태로 응답이 오는 것을 볼 수 있고..

다음과 같이 test page 경로에 대한 요청(route)은 가지만, 컴포넌트에서 요청하는
api 요청을 의미하는 콘솔은 한 번만 찍히는 걸 볼 수 있다.
결론
따라서 토큰이 사용되거나 개개인 마다 다른 내용이 필요한 경우, 새로운 정보가 매번 와야하는 경우엔 no-store을 사용해서 캐싱을 사용하지 않는게 좋을 것 같다.
캐싱을 공부하기 전엔 캐싱은 무조건 효율적이고 성능에 도움이 될 거라 생각했지만, 가장 먼저 캐싱은 꽤나 복잡한 개념으로 이루어져있다.
그리고 캐싱이 네트워크 성능면에선 좋을지 몰라도, 내가 의도한대로 안 흘러갈 수 있기에 적절한 캐싱전략을 사용해야 한다는 것을 깨달았다.
마지막으로, 정말 라이브러리 버전이 너무 빨리 바뀌고 버전마다 기본 값이나 작동 원리가 달라지는 경우가 빈번해 주기적인 학습과 기록의 중요함을 깨달았다.
본 글은 공식문서를 보고 이해 한 후, 공식문서의 내용과 내가 이해한 바를 합쳐서 필사한 글에 가깝다.

https://nextjs.org/docs/app/building-your-application/caching
Building Your Application: Caching | Next.js
An overview of caching mechanisms in Next.js.
nextjs.org
'코딩 > Flex프로젝트' 카테고리의 다른 글
재테크 블로그 프로젝트 (11) - 리팩토링 [getStaticProps, data fetching] (2) | 2025.01.03 |
---|---|
재테크 블로그 프로젝트 (10) - 리팩토링 [폰트 감소 번들사이즈 최종] (0) | 2025.01.02 |
재테크 블로그 프로젝트 (9) - 리팩토링 [번들 사이즈 줄이기] (0) | 2024.12.29 |
재테크 블로그 프로젝트 (8) - 리팩토링 [번들 사이즈 측정] (1) | 2024.12.28 |
재테크 블로그 프로젝트 (7) Lighthouse 성능 측정 및 개선 (0) | 2024.11.24 |