[ 살펴보기 ] NextJS - Caching

[ 살펴보기 ] NextJS - Caching

·

5 min read

NextJS App route가 제공하는 주요 cache기능은 Request Memorization, Data Cache, Full Route Cache, Route Cache. 이 네 가지다. NextJS 15 버전에서는 cache 관련 동작이 변경될 예정이며 해당 포스트는 NextJS 14를 기준으로 각 cache의 동작을 살펴본다.

Request Memorization

만약 특정 페이지를 구성하는 여러개의 component에서 fetch API를 통해 동일한 endpoint로 data fetch 요청을 하는 함수를 사용하고 있다면 제일 처음 fetch API를 호출한 함수의 return 값을 다른 component에서도 사용한다 다음의 예를 살펴보자.


import React, { PropsWithChildren } from "react";

export async function getOrder() {
  const res = await fetch('https://.../order')
  return res.json()
}

const Order = async () => {
  const orderData = await getOrder()
  return (
    <div>
      <OrderItem />
      <OrderCart />
      <OrderList />
    </div>
  );
};

export default Order;

만약 위의 예제에서 OrderItem, Ordercart, OrderList 역시 getOrder 함수를 통해 order data를 fetch하고 있는 상황이라면 order data에 대한 network request는 한번만 발생하고 OrderItem, Ordercart, OrderList component에서는 cache된 getOrder의 return값을 사용한다 ( Request Memorization )

그리고 Request memorization과 관련해 유의할 사항은 다음과 같다.

  • Request memorization은 get method를 사용하는 fetch api에 한해서 적용된다.

  • Request memorization은 동일한 React component tree 내에서 적용된다. ( 즉, 한 페이지 내에서 적용된다 )

Data Cache

NextJS App route에서 fetch api를 통해 fetch된 데이터는 cache에 저장되어 재사용된다. ( Server component에서 fetch api를 통해 fetch 했을 떄 )

즉, order라는 페이지에서 fetch api를 통해 특정 data를 fetch해서 해당 데이터가 cache가 되고 user라는 페이지에서 동일한 data를 fetch api를 통해 호출한다면 해당 data cache가 존재할 때 까지는 새로운 network reqeust를 만들지 않고 cache 데이터를 바로 사용한다. ( 특정 component tree에만 국한 되지 않는다는 점에서 request memorization과 차이점이 있다 )

만약 cache된 data를 revalidate하고 싶다면 두 가지 방법이 있다.


export async function getOrder() {
  fetch('https://...', { next: { revalidate: 60 } })
  return res.json()
}

...

NextJS에서 fetch api를 사용하면 위와 같이 next option에 revalidate이라는 option을 추가할 수 있다. 위의 예제와 같이 설정하면 data cache는 1분 마다 만기되어 해당 fetch를 호출하는 페이지나 컴포넌트로 다시 접근했을 때 cache 데이터를 사용하는 것이 아닌 새로운 network request를 발생시킨다

또는 다음과 같이 페이지 level에 revalidate segment config option을 추가 해도된다 ( segment config option은 data cache와 full route cache 둘 다 만기 시킨다 )

export const revalidate = 60

export async function getOrder() {
  fetch('https://...')
  return res.json()
}

...

만약 버튼과 같은 특정 event를 통해 data cache를 만기 시키고자 한다면 NextJS에서 제공하는 revalidateTag나 revalidatePath util을 사용할 수 있다

import { revalidateTag } from "next/cache";

export async function getOrder() {
  fetch('https://...', { next: { tags: ['order'] } })
  return res.json()
}

const handleRevalidateOrderData = () => {
    revalidateTag('order')
}

위의 예제에서 handleRevalidateOrderData 함수를 실행시키면 getOrder 함수에서 실행하는 fetch api의 data cache는 revalidate되고 해당 fetch api를 사용하는 page나 component로 다시 접근하면 cache 데이터를 쓰는것이 아닌 다시 network request를 발생시키는 것을 확인할 수 있다

만약 data cache와 Full Route Cache는 모두 invalidate 시키고 싶다면 revalidatePath를 통해 둘 다 revalidate 시킬 수 있다

import { revalidatePath } from "next/cache";

const handleRevalidateOrderRouteAndData = () => {
    revalidateTag('/order')
}

위의 handleRevalidateOrderRouteData 함수는 order라는 페이지의 route cache와 data cache를 모두 revalidate 시킨다.

새로운 build를 배포해도 기존의 data cache는 그대로 유지될 수 있으므로 주의하자.

Full Route Cache

NextJS는 build시 static render가 적용되는 페이지는 각각 html파일과 React Server Component Payload ( RSCP )를 생성한다. 그리고 static render가 적용된 페이지 request가 들어올 때 마다 해당 페이지에 대한 html과 RSCP를 server에서 새로 생성하여 응답해주는 대신 build때 생성했던 html파일과 RSCP를 전달한다.

Full Route cache는 만기기간이 별도로 존재하지 않는다. 즉, 특별한 조치를 하지 않는다면 build시 생성된 Full Route Cache가 계속해서 사용된다는 이야기다.

만약 특정 페이지의 Full Route Cache를 revalidate 하고 싶다면 해당 페이지가 의존하는 Data cache를 revalidateTag를 통해 revalidate 하거나 revalidatePath를 통해 페이지 path 자체를 revalidate 해준다. 혹은 새로운 build를 배포하는 방법도 있다.

주의할 점은 revalidateTag나 revalidatePath를 한다고 해서 바로 해당 페이지가 server에서 새로 render되지는 않는다. revalidate 이후 해당 페이지에 가서 browser refresh를 하거나 해당 page의 route cache가 만료된 이후 server에서 새롭게 render된 페이지가 제공된다.

다시 정리하자면, Full route cache는 dynamic render page에는 적용되지 않는다. Static render된 page이며 page에 사용된 data의 cache가 만기되지 않았고 revalidate segment가 적용되어 있다면 해당 revalidate segment 시간이 만기되지 않은 상태일 때 Full route cache가 적용된다.

Client-side Route Cache

NextJS는 client navigation을 통해 방문했던 page 혹은 prefetch된 page의 RSCP를 client측에 저장해두는데 이를 Route cache라고 한다.

만약 Route cache가 존재하고 만료되지 않은 상태일 때 client navigation을 통해 해당 페이지로 다시 이동하면 server에 요청없이 해당 페이지의 route cache를 바로 사용한다.

Route cache는 user session기간 동안 유지가 되므로 browser를 refresh하면 모두 사라진다. Default로 적용되는 route cache 유지 시간은 해당 route의 prefetch 형태에 따라 달라진다.

import Link from "next/link";

const LayoutNav = () => {
    return (
        <Link href="/user">User</Link>
        <Link href="/order" prefetch={true}>Order</Link>
    )
}

만약 navigation이 위의 예제와 같이 구성되어 있다면 user page의 route cache는 default로 30초간 지속되고 order 페이지와 같이 full prefetch를 하는 경우 route cache가 5분간 지속된다.

만약 Next 14.2 이전 version을 사용 중이라면 Default로 설정된 Route Cache 시간은 무조건적으로 적용이 된다. 그렇기에 default로 Route cache를 사용하지 않는 방법은 없다. 만약 route cache를 revalidate하고 싶으면 useRouter hook의 router.refresh를 통해 invalidate 해줄 수 있다.

여기서 route cache revalidate을 route page invalidate와 혼동하지 않도록 하자. router.refresh를 통해서 route cache를 revalidate한다고 해서 해당 page가 server에서 다시 render되어 제공되는 것은 아니다. 만약 page를 revalidate시켜 server에서 다시 render하고 route cache를 revaldiate하고 싶다면 revalidatePath을 통해 해당 페이지를 먼저 revalidate하고 useRouter hook의 router.refresh를 통해 route cache를 revalidate 시켜주자.

만약 Next 14.2 이후 버전을 쓰고 있다면 다음 next.config.js 설정을 통해 route cache의 staleTime 조절이 가능하다.

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
      static: 180,
    },
  },
}

module.exports = nextConfig

위에서 설명한 cache 동작방식은 다가오는 15 버전에서 다시 변경될 예정이므로 주의하자. - ( Next.js 15 RC - Caching updates )