퀴즈 프로젝트를 진행하는데 퀴즈 페이지들을 SSG 방식으로 빌드타임 때,만들어주고 있었습니다.
퀴즈 데이터를 하루마다 수정하고 있어 수정할 때마다 즉, 하루마다 배포를 진행해줘야하는데요.
이 과정이 꽤나 귀찮다고 느껴졌습니다.
그러던 찰나에 ISR이라는 것이 생각나 적용해보도록 하려합니다
ISR을 사용하면 전체 사이트를 다시 빌드하지 않고도 정적 콘텐츠 업데이트가 가능합니다. 즉,사이트 전체를 다시 빌드할 필요 없이 특정 페이지의 정적 콘텐츠를 주기적으로 갱신할 수 있습니다.
서버가 일정 시간 주기마다 요청된 페이지에 한해 데이터를 갱신하여 페이지를 다시 생성해주는 방식입니다. 하지만 이 과정은 모든 페이지가 아닌, 요청이 들어온 페이지에만 적용됩니다.
저의 경우에는 next 웹 서버를 AWS EC2에 배포하였는데요.
ISR 캐시는 기본적으로 서버의 디스크(파일 시스템)에 저장됩니다.
예를 들어, AWS EC2 같은 서버를 사용해 Next.js를 배포했다면, ISR 캐시는 서버의 디스크에 저장됩니다.
1. 빌드 시점
next build 단계에서는 generateStaticParams 또는 알려진 경로에 대해 정적 HTML 페이지를 미리 생성합니다.
이 과정에서 생성된 페이지는 캐시에 저장되고, 클라이언트 요청 시 즉시 제공됩니다.
2. 요청 처리와 캐시 확인
빌드 이후 클라이언트 요청이 발생하면:
캐시에 해당 페이지가 있으면 캐시된 정적 페이지를 즉시 반환합니다.
이 과정에서는 서버에서 별도의 데이터를 요청하거나 페이지를 다시 생성하지 않습니다.
3. 캐시 유효성 검증 (Revalidation)
캐시의 유효 기간(예: revalidate: 60)이 지나면 클라이언트 요청이 들어올 때 캐시를 무효화합니다.
백그라운드에서 서버가 새로운 데이터를 요청하여 페이지를 다시 생성합니다.
이 동안 클라이언트는 기존 캐시된 페이지(오래된 데이터)를 계속해서 볼 수 있습니다.
4. 새로운 페이지 생성
백그라운드 작업이 완료되면, 새로 생성된 페이지가 캐시에 저장되고, 이후 요청부터 갱신된 페이지가 제공됩니다.
그렇다면 저의 퀴즈 페이지에 적용해보도록 하겠습니다.
우선 기존 SSG 방식의 코드입니다.
import QuizDetails from "@/app/(page)/quiz/[detailUrl]/_components/client/quizDetails";
import {quizApiHandler} from "@/app/services/quiz/QuizApiHandler";
import {Metadata} from "next";
import React from 'react';
/**
* 퀴즈 문제 페이지
* 정적 렌더링 방식
*/
// SSG 실행할 페이지 ID 추출, 서버에 받아오는 PK들은 모두 SSG 방식으로 구현
export async function generateStaticParams() {
const {data} = await quizApiHandler.fetchQuizDetailUrlList({cache:"no-store"});
return data.map((url) => ({detailUrl:url}))
}
// SEO를 위해 메타데이터(title, description) 설정
export async function generateMetadata({
params
}:{
params:{
detailUrl:string
}
}):Promise<Metadata>{
const detailUrl = (await params).detailUrl
const {data} = await quizApiHandler.fetchQuizDetailByUrl(detailUrl)
return {
title:data.metaTitle,
description:data.metaDescription,
alternates:{
canonical:`/quiz/${data.detailUrl}`
}
}
}
const Page = async ({
params
}:{
params:{
detailUrl:string
}
}) => {
const { detailUrl } = await params
const {data} = await quizApiHandler.fetchQuizDetailByUrl(detailUrl)
return (
<QuizDetails
quizData={data}
/>
);
};
export default Page;
generateStaticParams 함수를 사용하여 빌드타임때 생성할 페이지 params를 지정해줍니다. 이후 빌드,배포를 하면 제가 생성하고자 하는 페이지들이 다 완성되있죠.
이제 ISR 방식을 적용해보겠습니다.
코드 한줄만 추가하면 끝이네요.
import QuizDetails from "@/app/(page)/quiz/[detailUrl]/_components/client/quizDetails";
import {quizApiHandler} from "@/app/services/quiz/QuizApiHandler";
import {Metadata} from "next";
import React from 'react';
/**
* 퀴즈 문제 페이지
* 정적 렌더링 방식
*/
export const revalidate = 86400 // 하루마다 갱신
// SSG 실행할 페이지 ID 추출, 서버에 받아오는 PK들은 모두 SSG 방식으로 구현
export async function generateStaticParams() {
const {data} = await quizApiHandler.fetchQuizDetailUrlList({cache:"no-store"});
return data.map((url) => ({detailUrl:url}))
}
// SEO를 위해 메타데이터(title, description) 설정
export async function generateMetadata({
params
}:{
params:{
detailUrl:string
}
}):Promise<Metadata>{
const detailUrl = (await params).detailUrl
const {data} = await quizApiHandler.fetchQuizDetailByUrl(detailUrl)
return {
title:data.metaTitle,
description:data.metaDescription,
alternates:{
canonical:`/quiz/${data.detailUrl}`
}
}
}
const Page = async ({
params
}:{
params:{
detailUrl:string
}
}) => {
const { detailUrl } = await params
const {data} = await quizApiHandler.fetchQuizDetailByUrl(detailUrl)
return (
<QuizDetails
quizData={data}
/>
);
};
export default Page;
추가된 코드는
export const revalidate = 86400
이것뿐입니다.
즉, 하루마다 페이지를 다시 빌드하여 갱신하겠다는거죠.
저는 퀴즈 데이터를 관리자에서 하루주기로 업데이트하기에 위와 같이 설정해주었습니다.
생각보다 사용하기에 너무 간편하지만 기능은 너무 유용한 것 같습니다. ISR 굳