이전 스터디를 하면서 next.js의 app-router가 안정이 된다면 fetch를 사용해야 된다 → axios의 경우에는 직접 캐싱 관련 설정을 해주어야 된다고 한다는 부분을 보았다. 저번에는 axios가 어떻게 node와 브라우저를 인식하는지에 중점을 둬서 작성하였고 fetch로 interceptor 기능을 만들 때는 어떻게 할 지와 관련하여 중점적으로 다루었다. 이번에는 Next.js의 Caching 부분에 관련하여 공부를 좀 더 해보고자 한다. 다음에는 가볍게 라이브러리 관련인 ky와 return-fetch으로..
Next.js의 성능을 최적화하는 방법을 이해하는 데 도움을 주려는 목적이라고 한다. 페이지 소개에서도 보면 최소한의 설정으로도 최상의 성능을 발휘할 수 있도록 기본값이 제공이 되기 때문에 모르고 사용해도 된다고 한다. 서버에 적용되는 캐시 매커니즘은 다음과 같다.
메커니즘 | 내용 | 위치 | 목적 | 지속 시간 |
---|---|---|---|---|
Request Memoization | fetch 함수의 return 값 | 서버 | React 컴포넌트 트리에서 데이터 재사용 | 요청 생명 주기 동안 |
Data Cache | 데이터 | 서버 | 사용자 요청 및 배포 간 데이터 저장 | 영구적(revalidate 가능) |
Full Route Cache | HTML 및 RSC(React Server Component) 페이지 로드 | 서버 | 렌더링 비용 절감 및 성능 향상 | 영구적(revalidate 가능) |
Router Cache | RSC 페이지 로드 | 클라이언트 | 네비게이션 시 서버 요청 감소 | 사용자 세션 또는 시간 기반 |
그림 요약 : 라우트가 빌드 타임에 정적으로 렌더링될 때와 정적 라우트가 처음 방문될 때의 캐싱 동작
캐싱 동작은 사용 사례에 따라 개별 라우트 및 데이터 요청에 대한 캐싱 동작을 구성할 수 있다.
웹 서버로 페이지 요청이 들어오면 페이지에 필요한 데이터들을 fetch하게 되는데, 이 때 동일한 URL과 옵션을 가진 요청을 자동으로 메모이제이션(memoization)하기 위해 fetch API를 확장한다.(이건 React에서 확장한 기능이라고 한다.) 같은 데이터를 여러 라우트에서 사용해야 하는 경우(예: 레이아웃, 페이지, 여러 컴포넌트에서), 상위 컴포넌트에서 API fetch 결과를 prop drilling 하는것 대신, 각 컴포넌트에서 fetch를 수행하도록 구현해도 실제 API 요청은 최초 1회만 전송되고 나머지는 응답값을 재사용한다.
// 예시 코드
async function getItem() {
// `fetch` 함수는 자동으로 memoized되며 결과값이 캐싱된다.
const res = await fetch('https://.../item/1');
return res.json();
}
// 컴포넌트 A
const item = await getItem(); // cache MISS
// 컴포넌트 B
const item = await getItem(); // cache HIT : getItem 함수의 실행 없이 캐싱된 return값을 받는다.
Request Memoization은 서버에서 호출되는 GET
메서드에만 적용되므로, **POST
**나 DELETE
API 또는 클라이언트에서 호출되는 API에는 적용되지 않는다. 그리고 한 번의 서버 렌더링 동안만 유효하기 때문에 따로 revalidate
할 필요가 없을 뿐 아니라 할 수도 없다.
기본적으로 fetch를 사용하는 데이터 요청은 캐시되지 않기 때문에 캐싱 동작을 구성하려면 fetch의 cache 및 next.revalidate 옵션을 사용하여 동작하게 할 수 있다. 성공적으로 데이터를 가져왔다면 그 응답값을 저장해두었다가 동일한 경로로 fetch
함수를 실행할 때 실제 API 호출은 건너뛰고 저장해놓은 응답값을 반환하는 형태이다.
만약, 하나의 요청 동안만 유효한 Request Memoization과 다르게 Data Cache는 일정 시간 동안에 웹 서버로 들어오는 모든 요청에 대해 동작한다. 만약 **next.revalidate
**를 1초로 설정했다면, 1초에 1000명의 사용자가 접속해도 실제 API 요청은 1회 전송된다는 뜻!
주의할 점
revalidate 시간이 지나더라도 첫 요청은 캐싱된 값을 (STALE 상태여도) 반환한다. → 그 이유는 사용자의 빠른 응답 선호 때문, 또한 실시간이 아니라면 서버 부하를 계속해서 줄 필요가 X 반환 후 백그라운드에서 API를 호출해서 값을 업데이트하는데, 개발자 의도와 다르게 동작할 수 있기 때문에 캐시를 적용할 때 주의가 필요하다.
해결방법으로는 router.refresh로는 Data Cache가 revalidate되지 않고, revalidatePath를 사용해야 한다고 합니다. (이때는 즉시 revalidate 되기 때문에, 다음 첫 요청에도 새로운 값을 반환한다.)