프론트 단에서 데이터를 효율적으로 관리하는 것은 매우 중요하다.
이를 위해 Cache를 흔히 활용한다. 이번에는 Next 에서 Cache를 효율적으로 활용하기 위해 공부해보자.
Request Memoization
공식문서에 따르면 요청 url과 options가 같은 fetch api 에 대해서 자동으로 기억(memoize) 한다고 나와있다.
즉, 위 그림처럼 동일한 endpoint로의 API fetch가 여러 컴포넌트에서 수행된다면 Request memoization이 동작한다.
따라서 API fetch 결과를 props drilling 하지 않고, 각 컴포넌트에서 fetch를 진행해도 실제 API 요청은 최초 1번만 시행된다.
다만, memoization은 서버에서 호출되는 GET 요청에 한해서 적용되며, POST나 DELETE API 또는 클라이언트에서 호출되는 API에는 적용되지 않는다.
memoization은 한번의 서버 렌더링 동안만 진행되므로 따로 revalidate 할 필요도, 할 수도 없다 .
이는 Next만의 특징이 아닌 React의 특징이고, 알아서 최대한 좋게 memoization을 진행해주므로 직접 건들일 일은 딱히 없을 것 같다.
Data Cache
Next 에서는 각 요청에서 얻은 데이터 결과물을 지속적으로 저장해놓을 수 있는 Data Cache 가 있다.
Next 에서의 fetch cache option은 서버 사이드 요청이 Next 서버의 Data cache와 어떻게 상호작용할지를 정하는 것으로, 브라우저에서의 http cache와 어떻게 상호작용할지 정하는 option과는 다르다.
하나의 요청 동안만 유효한 Request Memoization과 달리, Data Cache는 일정 시간 동안에 웹 서버로 들어오는 모든 요청에 대해 동작한다.
Time-based revalidation 에서의 Data Cache
만약 next.revalidate를 1초로 설정했다면, 1초에 1000명의 사용자가 접속해도 실제 API 요청은 1회만 전송된다.
위는 Time-based revalidation 에서 Data cache가 적용되는 flow이다.
그림에서 볼 수 있듯이, revalidate 시간이 지나더라도 첫 요청은 캐싱된 값을 (STALE 상태여도) 반환한다.
On-demand revalidation 에서의 Data Cache
위 flow에서 Staled data를 보여주는 Time-based revalidation와 다르게 동작하는 것을 볼 수 있다.
Full Route Cache
웹 서버의 성능을 눈에 띄게 향상시키려면 Full Route Cache를 적용해야 한다.
서버 렌더링 과정에서 웹 서버의 리소스(특히 CPU)를 대부분 사용하게 되는데, Full Route Cache는 서버 렌더링 결과를 재사용하기 때문에 사용되는 리소스를 줄일 수 있다.
위 그림은 Full Route Cache 동작 예시이다.
Full Route Cache를 적용하려면 페이지를 Static 렌더링 되도록 구성해야 한다.
이를 위해서는 Dynamic Function을 사용하지 않아야 하는데, 그렇지 않으면 그림과 같이 Full Route Cache 단계가 SKIP 되므로 RSC Payload와 HTML을 request 타임에 동적으로 그때그때 불러와야 한다.
Full Route Cache를 적용하기 위한 삽질기는 추후 블로그에 작성할 예정이다!!
Router Cache
Next 에는 서버 사이드에 Full Route Cache 라는 cache 장치가 있고, 마찬가지 클라이언트 사이드에서도 Router Cache 라는 cache 장치가 있다.
Router Cache에는 RSC Payload가 cache 되어 저장된다.
분명 위에서 Full Route Cache 에서도 RSC Payload가 저장된다고 했는데 무슨 차이일까?
위 그림은 Router Cache가 동작하는 예시이다.
Router Cache는 RSC Payload 를 user session 동안만 저장해놓는다. 반면에 Full Route Cache는 RSC Payload와 HTML을 지속적으로 저장한다.
또한 Full Route Cache는 정적 라우트에 한해서 caching을 진행하고 다수의 유저에게 cache를 제공할 수 있다. 반면, Router Cache는 동적 라우트에 관한 정보까지 caching한다는 특징이 있고, user session 동안만 저장하므로 개인의 브라우저에서만 적용되는 것이다.
클라이언트 사이드에서의 Revalidation
Next 작업을 하게 되면 POST 요청을 통해 바뀐 데이터를 UI에 바로 띄우고 싶은 경우가 많다.
우선 클라이언트 사이드에서의 revalidation을 진행하는 코드를 봐보자.
const handleButtonClick = async () => {
const dataCurationMake = getSendingCurationData();
const res = await fetch("/api/curation/make", {
method: "POST",
body: JSON.stringify(dataCurationMake),
});
if (res.ok) {
revalidateMyCuration();
handleOpen(false);
} else alert("오류가 발생했습니다!");
return;
};
위는 스크랩 버튼을 누르면 나오는 모달에서 큐레이션 만들기를 완료하면 수행되는 로직이다.
import { getSession } from "@common/utils/session/getSession";
import revalidateMyCuration from "@feature/curation/actions/revalidateMyCuration";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
const auth_info = await getSession();
const token = auth_info?.data?.accessToken;
const res = await fetch(
`${process.env.NEXT_PUBLIC_SERVER_API}/api/v1/curation`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
}
);
if (res.ok) {
revalidateMyCuration();
const data = await res.json();
return NextResponse.json(data);
}
return NextResponse.json({ error: "Api Failed" }, { status: 400 });
}
위 코드는 버튼 클릭 시 fetch 하는 'api/curation/make' 경로에 있는 route.ts 파일 코드이다.
해당 모달에서는 현재 본인이 만든 큐레이션 데이터가 띄워지고, 이 데이터는 클라이언트 사이드에서 fetch 되고 있다.
큐레이션을 만드는 요청이 성공적으로 수행(res.ok)되면 캐시된 내가 만든 큐레이션 데이터를 재검증하기 위해 revalidateMyCuration() 함수가 수행된다.
"use server";
import { revalidateTag } from "next/cache";
export default async function revalidateMyCuration() {
revalidateTag("getMyCuration");
}
위는 revalidateTag로 cache를 invalidate 하는 함수이다.
클라이언트 사이드에서 revalidateTag 등과 같은 서버 액션은 위와 같이 파일로 정의한 뒤 호출해야 한다.
const getMyCurationList = async () => {
const res = await fetch("/api/curation/my");
setCurationMy(await res.json());
};
useEffect(() => {
getMyCurationList();
}, []);
위는 내 큐레이션을 Route Handler를 통해 불러오는 코드이다.
'api/curation/my' 경로에 있는 route.ts 파일에는 tag가 'getMyCuration' 인 GET 메소드가 정의되어있다.
그럼 이제 revalidation이 진행된 후의 결과를 봐보자.
즉각적으로 만든 큐레이션이 모달 창에 나타나기를 기대했지만 바로 fresh한 데이터가 UI에 보이지 않는다.
해당 경로에 추후 재접속을 하거나 새로고침을 하고 다시 불러오면 그때서야 fresh한 데이터가 나타났다.
왜 이런 현상이 발생하는 것일까?
큐레이션 만들기를 완료하면 위와 같이 revalidation을 진행했으므로 서버에 있는 Data Cache가 invalidate 되고, 그에 따라 Full Route Cache 역시 invalidate 될 것이다.
클라이언트 사이드의 Router Cache 역시 invalidate 될 것이지만, 이 때 클라이언트 컴포넌트에서는 위에서 봤듯이 useEffect를 활용하여 데이터를 가져와야 한다.
즉 페이지가 다시 mount 되어야 서버 사이드에서 fetch한 fresh data가 UI에 보여질 수 있다는 것이므로 해당 경로에 추후 재접속을 할 때부터 fresh한 데이터를 UI에 띄울 수 있는 것으로 결론을 내렸다.
결론: 클라이언트 사이드(클라이언트 컴포넌트)에서 가져오는 데이터를 revalidate 하는 경우, 해당 경로에 재접속을 해야지만 fresh한 데이터를 UI에 띄울 수 있다.
서버 사이드에서의 Revalidation
그럼 이제 서버 사이드에서의 revalidation에 대해 알아보자.
위 내용과 다른점은 없고 데이터를 불러오는 동작을 Route Handler가 아닌 다른 파일을 만들어서 선언 후 호출해서 사용하면 된다.
위에서 언급한 코드와 api는 같은 내 큐레이션 데이터에 접근한다.
서버 사이드에서도 위에서 만들어 놓은 revalidation 동작 파일을 호출해서 사용하였다.
위처럼 즉각적으로 fresh한 데이터가 UI에 반영되는 것을 볼 수 있다.
이때는 Data Cache가 invalidate 됨에 따라 Full Route Cache 역시 invalidate 되는데, 서버 컴포넌트는 js 파일이 hydration되는 과정을 거칠 필요가 없으므로 UI에 바로 반영되는 듯 하다.
결론: 서버 사이드(서버 컴포넌트)에서 가져오는 데이터를 revalidate 하는 경우, UI에 바로 fresh한 데이터가 띄워진다.
클라이언트 사이드에서 UI가 바로 업데이트 안되는 문제점이 발생하는 원인이 명확하게 나와있는 정보를 찾지못하여 직접 테스트와 공부를 해가며 정리해보았다.
데이터 관리에 대해 정리한 블로그가 추후에 나에게, 혹은 누군가에게는 도움이 되었으면 좋겠다!! 보완할 점이 있다면 추후에 보완해야겠다~
참고
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
https://fe-developers.kakaoent.com/2024/240418-optimizing-nextjs-cache/
Next.js 캐싱으로 웹 서버 성능 최적화 | 카카오엔터테인먼트 FE 기술블로그
남윤복(kiwi) 초등학생 때부터 장래희망 칸에 프로그래머라고 적었는데, 그 이유를 개발자로 일하면서 찾아가고 있습니다.
fe-developers.kakaoent.com
'FrontEnd > Next' 카테고리의 다른 글
Next 성능 개선 일지 2 (Feat: FOUT, bundle-analyzer, Link prefetch) (0) | 2024.05.04 |
---|---|
Next 성능 개선 일지 1 (Feat: Lighthouse, Next/Image) (2) | 2024.04.18 |
Next 에서 소셜 로그인 구현하기(Feat : Cookie) (4) | 2024.03.14 |
Next + TailwindCSS 세팅하기 (2) | 2023.12.28 |