0️⃣ Next.js의 캐싱 매커니즘 분류
매커니즘 | 설명 | 위치 | 목적 | 지속 기간 |
---|---|---|---|---|
Request Memoization |
서버에서 함수의 반환 값을 메모이제이션 하여 저장한다. | Server | 리액트 컴포넌트 트리에서 데이터를 재사용하기 위함 | 요청 라이프 사이클 동안 (각 요청별로 유지) |
Data Cache | 서버에서 데이터 자체를 저장한다. | Server | 사용자 요청과 배포 간에 데이터를 유지하여 불필요한 데이터 재요청을 줄인다. | 영구적이며, 필요에 따라 재검증(revalidation) 할 수 있다. |
Full Route Cache |
서버에서 HTML과 RSC Payload를 캐시한다. | Server | 경로를 미리 렌더링하여 렌더링 비용을 줄이고 성능을 향상 시킨다. | 영구적이며, 필요에 따라 재검증(revalidation) 할 수 있다. |
Router Cache | 클라이언트 측에서 RSC Payload를 캐시한다. | Client | 사용자가 페이지를 탐색할 때 서버 요청을 줄여 성능을 개선한다. | 사용자가 브라우저를 종료하기 전까지 유지되거나, 지정한 시간이 지나면 갱신되는 방식 |
- 기본적으로 Next.js는 성능을 개선하고 비용을 줄이기 위해 가능한 한 많이 캐시한다.
- 경로는 정적으로 렌더링 되고, 데이터 요청은 옵트아웃 하지 않는 한(기본 설정을 거부하지 않는 한) 캐시된다.
- 캐싱 동작은 경로가 정적으로 렌더링되는지 동적으로 렌더링되는지, 데이터가 캐시되는지 캐시되지 않는지, 요청이 초기 방문의 일부인지 후속 탐색의 일부인지에 따라 달라진다.
- 아래는 Next.js의 기본 캐싱 동작을 나타낸 다이어그램이며, 빌드 시 경로가 정적으로 렌더링될 때와 정적 경로가 처음 방문될 때 이다.
1️⃣ Request Memoization
개요
- React는 동일한 URL과 옵션이 있는 요청을 자동으로 메모하기 위해서
fetch
API를 확장한다. - 따라서 React 컴포넌트 트리의 여러 곳에서 동일한 데이터에 대한 fetch 함수를 호출하면서도 한 번만 실행할 수 있다.
- ex) 경로 전체에서 동일한 데이터를 사용해야하는 경우(레이아웃 페이지 및 여러 컴포넌트) 트리의 맨 위에서 데이터를 fetch 하고 컴포넌트 간에 props를 전달하지 않아도된다. 동일한 데이터에 대해서 여러 요청을 하는 성은에 대한 걱정 없이 데이터 페칭 가능
fetch
API의 GET 메서드에만 적용된다. POST,DELETE 는 메모이제이션 되지 않음
async function getItem() {
// The `fetch` function is automatically memoized and the result
// is cached
const res = await fetch('https://.../item/1')
return res.json()
}
// 첫번째 실행에서는 원래대로 동작함 => cache MISS
const item = await getItem();
// 어느곳에서나 두번째 실행에서는 캐싱된 값을 사용홤 => cache HIT
const item = await getItem(); // cache HIT
Request Memoization 작동 방식
- 경로를 렌더링하는 동안 특정 요청이 처음 호출되면 결과는 메모리에 저장되지 않고 캐시에 저장된다. (cache MISS)
- 따라서 해당 함수가 실행되고, 외부 소스에서 데이터를 가져오고, 그 결과가 메모리에 저장된다.
- 동일한 렌더 패스에서 요청에 대한 후속 함수 호출은 캐시가 되고, 함수를 실행하지 않고도 메모리에서 데이터가 반환된다. (cache HIT)
- 경로가 렌더링되고 렌더링 패스가 완료되면 메모리가 '재설정'되고 모든 요청 메모 항목이 지워진다.
(요청 메모이제이션 작동방식 사진 첨부하기)
Good to knows
- Request Memoization은 Next.js 기능이 아닌 React 기능이다.
- 메모이제이션은 fetch 요청의 GET 메서드에만 적용 된다.
- 메모이제이션은 React 컴포넌트 트리에만 적용된다.
generateMetadata
,generateStaticParams
,Layouts
,Pages
,Server Components
에 적용 가능Route Handlers
요청에는 적용되지 않는다 -> React 컴포넌트가 아니기 때문
fetch
API 가 적합하지 않은 경우(some database clients, CMS clients, GraphQL clients) Reactcache
함수를 사용할 수 있다.
2️⃣ Data Cache
개요
- Next.js에는 들어오는 서버 요청과 배포에서 데이터 페치 결과를 유지하는 내장형 데이터 캐시가 있다.
- 이는 Next.js가 네이티브
fetch
API를 확장하여 특정 요청의 캐싱 동작을 설정하거나, 가져온 데이터를 일정 기간 동안 저장(캐싱)해 둘 수 있다. - 이 캐시는 단순히 한 요청 사이클 동안만 유지되는 것이 아니라, 서버간 요청이나 배포 간에도 유지된다. 따라서 서버에 새 요청이 들어와도, 동일한 데이터에 대해 매번 새롭게 요청하지 않고 기존 데이터를 재사용한다.
fetch
API를 통해 각 요청마다 캐싱 동작을 제어할 수 있다.- 데이터를 항상 최신 상태로 유지하고 싶다면
cache:'no-stroe'
로 설정하거나,revalidate
를 짧게 설정한다. - 데이터를 자주 바뀌지 않는 것으로 간주하면 더 긴 기간 캐시를 유지한다.
- 정적인 데이터라면
cahce:'force-cache'
로 설정한다.
- 데이터를 항상 최신 상태로 유지하고 싶다면
데이터 캐시 작동 방식
- 처음에 렌더링 중에
'force-cache'
옵션이 포함되어있는fetch
요청이 호출된다면, Next.js는 데이터 캐시를 캐시된 응답이 있는지 체크한다. - 캐시된 응답을 찾았다면, 즉시 반환하고 메모화된다.
- 캐시된 응답을 찾지 못했다면 데이터 소스에 요청이 이루어지고, 그 결과를 데이터 캐시에 저장하고 메모화 한다.
- 캐시되지 않은 데이터(cache 옵션이 정의되지 않았거나
{cache:'no-store'}
옵션을 사용)의 경우 결과는 항상 데이터 소스에서 가져와서 메모화 된다. => Data Cache는 스킵된다. - 데이터가 캐시되든 캐시되지 않든, 요청은 항상 메모화 되어 React 렌더 단계 중에 동일한 데이터에 대한 중복 요청이 발생하지 않도록 한다.
Data Cache와 Request Memoization의 차이점
- Data Cache는 들어오는 요청과 배포 전체에서 영구적으로 유지된다.
- Request Memoization는 요청의 수명 동안만 지속된다.
재검증(Revalidating)
- Time-based Revalidation
- 일정 시간이 지나고 새로운 요청이 이루어진 후 데이터를 재검증한다. (변경 빈도가 낮고 신선도가 그렇게 중요하지 않은 데이터에 유용)
fetch
에서 리소스의 캐시 수명을 설정하는next.revalidate
옵션을 사용한다.fetch
를 사용하지 못하는 경우 Route Segment Config 옵션 설정을 통해 세그먼트의 모든 GET 요청을 구성할 수 있다.
// Revalidate at most every hour fetch('https://...', { next: { revalidate: 3600 } }) // app/products/route.js // /products 경로에 대한 모든 GET 요청은 1시간 동안 캐시된다 // fetch를 명시적으로 사용하지 않아도 이 설정이 해당 경로의 모든 요청에 대해 적용된다. export const revalidate = 3600; // (Route Segment Config)`
- 작동 방식
- 처음 호출되면 외부에서 데이터를 가져와 데이터 캐시에 저장
- 지정된 시간내에 호출되는 요청은 캐시된 데이터 반환
- 해당 시간이 지난 후 다음 요청은 캐시되었던 데이터 반환
- 백그라운드에서 데이터 재검증 트리거 -> 데이터를 성공적으로 가져왔을때 데이터 캐시를 최신 데이터로 업데이트, 재검증 실패시 이전 데이터 유지
(시간기반 재검증 작동방식 사진 첨부하기)
- On-demand Revalidation
- 이벤트에 따라 데이터를 재검증한다. (최신 데이터가 가능한 한 빨리 표시되도록 하려는 경우에 유용)
- 경로(revalidatePath) or 캐시 태그(revalidateTag) 를 통해 재검증할 수 있다.
- 작동 방식
- 처음 호출되면 외부에서 데이터를 가져와 데이터 캐시에 저장
- on-demand 재검증이 트리거 되었을때, 해당 캐시 항목이 캐시에서 제거된다.
- 다음에 요청이 이루어지면 다시 캐시가되고, 데이터는 외부에서 데이터를 가져와 데이터 캐시에 저장된다.
(on-demand 재검증 작동방식 사진 첨부하기)
3️⃣ Full Route Cache
개요
- Next.js는 빌드 시 자동으로 경로를 렌더링하고 캐시한다.
- 모든 요청에 대해 서버에서 렌더링하는 대신 캐시된 경로를 제공할 수 있는 최적화로서 결과적으로 페이지 로드가 더 빨라진다.
- Full Route Cache가 어떻게 작동하는지 이해하려면 React가 렌더링을 처리하는 방식과 Next.js가 결과를 캐시하는 방식을 살펴보는 것이 좋다.
3-1. 서버에서 React 렌더링
- 서버에서 Next.js는 React의 API를 사용하여 렌더링을 조정한다. 이때 렌더링 작업은 개별 경로 세그먼트와 Suspense 경계에 따라 청크로 나뉜다.
- 각 청크는 두 단계로 렌더링 된다.
- React는 스트리밍에 최적화된 특수 데이터 형식인 React Server Component Payload 로 서버 구성요소를 렌더링한다.
- Next.js는 React Server Component Payload와 Client Component Javascript 명령어를 사용하여 서버에서 HTML을 렌더링한다.
- 즉 작업을 캐싱하거나 응답을 보내기 전에 모든 것이 렌더링될 때까지 기다릴 필요가 없다. 대신 작업이 완료되면 응답을 스트리밍할 수 있다.
❓ React Server Component Payload 란 무엇일까 ❓
- RSC Payload는 렌더링된 React Server Components 트리의 컴팩트한 바이너리 표현이다.
- 클라이언트에서 React가 브라우저의 DOM을 업데이트하는 데 사용된다.
- RSC Payload의 구성요소
- 서버 컴포넌트의 렌더링된 결과
- 클라이언트 구성요소를 렌더링해야 하는 위치와 해당 Javascript 파일에 대한 참조
- 서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 모든 props
(서버 컴포넌트에 대한 자세한 내용은 추후에 다루도록 하겠다..!)
3-2. 서버에서 Next.js 캐싱(전체 경로 캐시)
- Next.js의 기본 동작은 경로의 렌더링된 결과(RSC Payload, HTML)를 서버에 캐시하는 것이다.
- 이는 빌드 시 또는 재검증 시 정적으로 렌더링된 경로에 적용된다.
3-3. 클라이언트에게 Hydration 및 Reconciliation을 시킨다.
- HTML은 클라이언트 및 서버 컴포넌트의 비대화형 초기 미리보기를 빠르게 보여주는 데 사용된다.
- RSC Payload는 클라이언트와 렌더링된 서버 컴포넌트 트리를 Reconciliation 하고 DOM을 업데이트 하는데 사용된다.
- Javascript 지침은 Hydration에 사용된다. 클라이언트 컴포넌트를 사용하여 애플리케이션을 대화형으로 만든다.
3-4. 클라이언트에서의 Next.js 캐싱(라우터 캐시)
- RSC Payload는 클라이언트 측 Router Cache에 저장된다.
- 이는 개별 경로 세그먼트로 분할된 별도의 메모리 내 캐시이다.
- Router Cache는 이전에 방문한 경로를 저장하고 향후 경로를 미리 페치하여 탐색 환경을 개선하는 데 사용된다.
3-5. 이후의 동작
- 이후 탐색이나 prefetch 중에 Next.js는 RSC Payload가 Router Cache에 저장되어 있는지 확인한다. 그렇다면 서버에 새 요청을 보내는 것을 건너뛴다.
- 경로 세그먼트가 캐시에 없으면 Next.js는 서버에서 RSC Payload를 가져와 클라이언트의 Router Cache에 채운다.
정적 및 동적 렌더링
- 경로가 빌드 시간에 캐시되는지의 여부는 정적 또는 동적으로 렌더링 되는지에 따라 달라진다.
- 정적 경로는 기본적으로 캐시된다. ex)
/products
,/about
- 동적 경로는 요청 시간에 렌더링되고 캐시되지 않는다. ex)
/products/[id]
,/user/[username]
- 정적 경로는 기본적으로 캐시된다. ex)
- 아래의 다이어그램은 캐시된 데이터와 캐시되지 않은 데이터를 사용하여 정적 및 동적으로 렌더링된 경로 간의 차이점을 보여준다.
- 정적 경로는 접근한 경로에 대하여 Full Route Cache에서 캐싱됨
- 동적 경로는 접근한 경로에 대하여 Full Route Cache를 건너뛰고, Data Cache나 Data Source에 캐싱됨
전체 경로 캐시를 무효화할 수 있는 방법
- 데이터 캐시를 재검증
- Full Route Cache는 HTML과 React Server Component(RSC) Payload를 캐싱한다.
- 데이터 캐시(Data Cache)는 Full Route Cache의 렌더링 결과를 구성하는 데 사용됩니다.
- 따라서 데이터 캐시를 재검증하면 새로운 데이터를 기반으로 서버가 페이지를 다시 렌더링하고, Full Route Cache를 업데이트한다.
- 데이터에 의존하지 않는 정적 경로는 데이터 캐시와 무관하므로 이 경우에는 Full Route Cache가 무효화되지 않습니다.
- 재배포
- Full Route Cache는 배포마다 초기화된다.
- 배포 시에 캐싱된 HTML과 RSC Paylaod가 모두 삭제되고 서버가 처음 요청을 받을 때 다시 렌더링을 통해 새롭게 Full Route Cache를 채우기 때문
옵트아웃
- 모든 수신 요청에 대해 구성요소를 동적으로 렌더링(full route cache를 해제) 하는 방법
- 동적 API 사용(cookies,header 등등)
dynamic = 'force-dynamic'
또는revalidate = 0
경로 세그먼트 구성 옵션 사용 (full route cache, data cache 를 건너뜀)- 데이터 캐시에서 옵트아웃: 특정 fetch 요청에서 데이터 캐시를 사용하지 않으므써 full route cache도 비활성화 하는 방법 (
fetch
에 옵션을 주어 SSR 또는 ISR 방식 채택)
4️⃣ Router Cache
개요
- Next.js는 레이아웃, 로딩상태, 페이지로 분할된 경로 세그먼트의 RSC Payload를 저장하는 메모리 내 클라이언트 측 라우터 캐시를 갖고있다.
- 사용자가 경로 사이를 탐색할 때 Next.js는 방문한 경로 세그먼트를 캐시하고, 사용자가 탐색할 가능성이 높은 경로를 미리 가져온다.
- 그 결과 즉각적인 뒤로/앞으로 탐색이 이루어지고, 탐색 사이에 전체 페이지를 다시 로드하지 않으며, React 상태와 브라우저 상태가 보존된다.
- 라우터 캐시의 사용
- 레이아웃은 캐시되어 탐색 시 재사용된다(부분 렌더링)
- 로딩 상태는 캐시되어 즉각적인 탐색을 위해 탐색 시 재사용된다.
- 페이지는 기본적으로 캐시되지 않지만 브라우저 뒤로 및 앞으로 탐색하는 동안 재사용된다.
- 캐시는 브라우저의 임시 메모리에 저장된다. 따라서 브라우저 새로고침시 캐시가 지워지게된다.
- 레이아웃, 로딩 상태 캐시는 특정 시간 후에 자동으로 무효화된다.
- 기본 프리페칭(
prefetch={null}, 미지정
): 동적 페이지는 캐시되지 않음, 정적 페이지의 경우 5분 - 전체 프리페칭(
prefecth={true}
또는router.prefetch
): 정적 및 동적 페이지 모두 5분
- 기본 프리페칭(
라우터 캐시 작동방식
- 사용자가 경로 사이를 탐색할 때 Next.js는 방문한 경로 세그먼트를 캐시하고, 사용자가 탐색할 가능성이 높은 경로를 미리 가져온다
( 컴포넌트의 뷰포트 기반) - 방문한 경로가 캐시되므로 즉시 뒤로/앞으로 탐색 가능하고, 프리페칭 및 부분 렌더링을 통해 새로운 경로로 빠르게 탐색 가능하다.
- 탐색 간에 전체 페이지를 다시 로드하지 않으며 React 상태와 브라우저 상태가 보존된다.
❓ Router Cache와 Full Route Cache의 차이점 ❓
- Router Cache
- 사용자 세션 동안 브라우저에 RSC Payload를 일시적으로 저장한다.
- 정적으로 렌더링된 경로와 동적으로 렌더링된 경로 모두에 적용된다.
- Full Route Cache
- 여러 사용자 요청에 걸쳐 RSC Payload와 HTML을 서버에 지속적으로 저장한다.
- 정적으로 렌더링된 경로만 캐시한다.
라우터 캐시를 무효화하는 방법
- 이미 클라이언트에 캐싱된 데이터를 무효화하여 새로운 데이터를 서버에서 가져오도록 강제하는 과정
- 캐싱된 데이터가 더 이상 유효하지 않을 때 수행할 수 있다.
- 서버에서 무효화
revalidatePath
를 사용하여 특정 경로의 캐시를 무효화(on-demand revalidation)revalidateTag
을 사용하여 특정 태그가 포함된 데이터 캐시 무효화- cookies 를 사용한 라우터 캐시 무효화
- 클라이언트에서 무효화
router.refresh
를 통한 무효화 => 클라이언트 측에서 Router Cache를 무효화하고, 현재 경로의 데이터를 다시 서버에서 가져오도록 요청
📝 정리
📌 Request Memoization
- React 컴포넌트 트리의 여러 곳에서 동일한 데이터에 대한 fetch 함수를 호출할 수는 있지만 실제로 한 번만 실행된다.
- React 서버 컴포넌트의 렌더링 중에 발생하는 fetch 요청을 메모이제이션한다.
- 메모이제이션된 `fetch` 요청은 렌더링 패스가 끝나면 초기화된다.
- GET 요청시에만 적용된다.
📌 Data Cache
- 데이터를 일정 기간 동안 캐싱하고, 필요에 따라 재검증(revalidation)을 수행하고 싶을 때 사용한다.
- 서버 간 요청과 배포 간에도 데이터를 유지하여 불필요한 데이터 요청을 줄인다.
- `fetch` API의 `revalidate` 나 `cache` 옵션을 통해 캐싱 동작을 제어할 수 있다.
- cache: 'force-cache': 데이터를 항상 캐싱(SSG).
- cache: 'no-store': 데이터를 실시간으로 가져오며, 캐싱하지 않음(SSR).
- next: { revalidate: N }: N초마다 데이터 재검증(ISR).
📌 Full Route Cache
- 서버에서 HTML과 RSC Payload를 생성한 뒤, 이를 여러 사용자 요청에 재사용하고 싶을 때 사용한다.
- 서버에서 경로의 HTML과 React Server Component(RSC) Payload를 캐싱하여 정적인 경로에서 빠른 페이지 로드를 제공한다.
- 데이터 캐시가 변경되거나 재배포 시 캐시가 초기화된다.
📌 Router Cache
- 클라이언트에서 페이지 탐색 시 빠른 응답 속도가 필요하거나, 사용자 세션 동안 데이터를 캐싱하고 뒤로/앞으로 탐색 시 즉각적으로 데이터를 로드하려는 경우 사용한다.
- 클라이언트 측에서 RSC Payload를 캐싱하여 서버 요청을 줄이고 페이지 탐색 속도를 높인다.
- 사용자 세션 동안 유지되며, 브라우저 새로고침 시 초기화된다.
- Link 컴포넌트를 이용한 프리페칭이 대표적인 예시
🔥 전체적인 캐싱 흐름
1️⃣ 빌드 단계
- 정적인 HTML과 RSC Payload를 생성하여 Full Route Cache 에 저장
- 정적인 경로, 정적인 페이지들이 적용 대상이다.
2️⃣ 요청 처리 단계(Server-side)
- Request Memoization 을 이용해 서버 렌더링 과정 중 동일한 fetch 요청이 여러 번 호출되더라도 한 번만 실행되도록 자동으로 메모이제이션 한다.
- Data Cache를 이용해 fetch 요청 결과를 저장하여 동일한 데이터 요청을 처리하고, revalidate 설정을 통해 갱신 주기 제어
3️⃣ 렌더링 단계(런타임)
- 내부 데이터의 재검증이 필요하다면 Full Route Cache가 사용된다. (ISR)
- 동적인 경로이거나, 매 요청마다 데이터를 갱신해야 한다면 Full Route Cache를 건너뛴다. (SSR, '/products/[id]')
4️⃣ 클라이언트 탐색 단계
- Router Cache를 이용해 RSC Payload를 클라이언트 메모리에 캐싱하여 동일한 경로를 탐색할때 서버 요청을 건너뛰고 빠르게 렌더링한다.
5️⃣ 무효화 단계
- 캐시가 더 이상 유효하지 않을때, 데이터를 강제로 갱신한다.
- 서버에서의 무효화: revalidatePath/revalidateTag(Data Cache, Full Route Cache 무효화), 쿠키 변경 메서드(Router Cache 무효화)
- 클라이언트에서의 무효화: router.refresh(Router Cache 무효화)
6️⃣ 배포 단계
- 빌드된 결과물을 CDN을 통해 제공
- Full Route Cache 초기화
- Data Cache는 배포간에 유지되므로 데이터가 변경되지 않았다면 새로운 요청에서도 동일한 데이터 사용 가능
📚 REFERENCE
'FE > NextJS' 카테고리의 다른 글
NextJS 공식문서 파헤치기 (Middleware) (1) | 2024.12.17 |
---|---|
NextJS 공식문서 파헤치기 (Error Handling) (0) | 2024.12.10 |
NextJS 공식문서 파헤치기 (Linking and Navigating) (5) | 2024.11.14 |
NextJS 공식문서 파헤치기 (Defining Routes, Pages, Layouts and Templates) (1) | 2024.11.14 |
NextJS 공식문서 파헤치기 (Routing Fundamentals) (1) | 2024.11.13 |