thumbnail

next.js app router에 오래걸리는 API ISR 처리하기

생성일2024. 10. 26.
태그
작성자지한솔

notion image
처음 블로그를 구현할때 notion API를 활용하기 위해서 프로젝트를 구상하고, 기능 구현을 진행했습니다. 그런데, notion API를 사용하는 방식에 차이가 있어 API 요청이 매우 오래 걸리는 현상이 있었습니다.
그렇기 때문에 처음에 SSG로 블로그를 구현하는 방향으로 진행을 했고, 그렇기 때문에 app router의 generateStaticParams를 사용하게 되었습니다. 빌드 시점에 Api를 fetch 받고, 미리 경로를 생성하는 SSG 방식이였죠.

하루종일 걸리는 빌드

처음에 구현한 페이지는 API를 한번만 받아서, 리스트를 뿌려주었지만 점차 프로젝트를 확장하다보니 swiper형태의 UI, 등 여러곳에서 API를 받는 일이 늘어나게 되었습니다. 그 결과 빌드 시간이 30분이 넘어버리는 불상사가 생기게 되었습니다.
아니 이럴수가…
아니 이럴수가…
아니 무슨 배포가 30분이나.. 배포가 오래 걸릴 뿐 아니라 중간중간 빌드가 깨질때가 있어서 리스크가 매우 컷죠..
그래서 저는 API를 한번만 받는 캐싱처리를 진행하게 되었습니다.

문제의 시작..

let cachedPosts: TPosts | null = null; export async function getSSGPosts(): Promise<TPosts> { if (!cachedPosts) { cachedPosts = filterPosts(await getPosts()); } return cachedPosts; }
API는 filterPosts를 사용하였고, 매번 사용하는 곳에서 호출을 했습니다. 저 API가 매우 오래걸리는 API였는데 그렇기에 최초 1회 데이터를 받았다면 기존에 데이터를 사용하고 return하게 구현하게 되었죠.
 
여기서 문제가 발생했습니다. SSG에서 ISR로 옮기기 위해서 app router 컴포넌트에 해야하는 코드는 단 한줄
export const revalidate = 300; // 5분
위와 같은 코드였죠. 그런데 제 블로그 프로젝트에서는 저 내용이 동작하지 않았어요. 왜냐? cachedPosts가 이미 존재하기 때문에 매번 같은 값을 리턴하게 되기 때문입니다.
 
그렇다고 cachedPosts를 빼기에는 너무 쥐약이였습니다. 왜냐하면 빌드 시간이 기하 급수적으로 늘어나기 때문입니다.

왜 빌드가 오래걸리나요?

그렇다면 여기서 왜? 빌드 시간이 기하급수적으로 늘어날까요?
SSG를 위해 사용한 generateStaticParams 가 있습니다. 그 안에서도 페이지 notion 블록 데이터를 받기 위해서는 filterPosts를 받아야 하는 문제점이 있었습니다.
filterPosts는 모든 posts의 블록 데이터를 가지고 있는거였죠. 그렇기 때문에 게시글이 3개있다면 generateStaticParams 를 통해서 3개의 SSG페이지가 생기고 거기서 페이지 블록 데이터를 받기 위해서 filterPosts를 수행하게 되죠. 이로인해서 기하급수적으로 빌드 시간이 늘어나게 되었죠.

빌드의 타입을 알 수 있다면?

그렇다면 빌드 시간도 단축시키고 ISR로 정적 재생성을 진행해준다면 어떻게 할 수 있을까요? 우리는 빌드의 타입만 알 수 있다면 가능할 겁니다.
우리가 명령어로 수행하는 run build명령어의 타입과 ISR이 build하는 타입이 다르지 않을까? 라는 생각이 들었습니다. next든 react든 결국 빌드할때 prod인지 dev를 환경변수에 저장한다는 것을 기억하고 있기 때문에 왠지 있을 것 같다는 생각이 들었습니다.
next.js 의 환경변수 코드
next.js 의 환경변수 코드
실제로 nextjs의 코드를 추적하다보니 위와같은 내용을 볼 수 있었습니다.
export const PHASE_PRODUCTION_BUILD = 'phase-production-build' export const PHASE_PRODUCTION_SERVER = 'phase-production-server' export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server'
위와같은 빌드의 타입중 build명령어를 통해서 build할때의 환경변수값은 phase-production-build 으로, ISR이 빌드를 수행할땐 phase-production-server 라는 사실을 알 수 있었습니다.
그럼 우리는 이제 이 빌드의 환경변수에 따라 조건을 걸어주면 되는거죠.
let cachedPosts: TPosts | null = null; export async function getCachedPosts(): Promise<TPosts> { const isBuild = process.env.NEXT_PHASE === "phase-production-build"; return isBuild ? getSSGPosts() : getFreshPosts(); } export async function getSSGPosts(): Promise<TPosts> { if (!cachedPosts) { cachedPosts = filterPosts(await getPosts()); } return cachedPosts; } export async function getFreshPosts(): Promise<TPosts> { return filterPosts(await getPosts()); }
따라서, isBuild 라는 변수를 통해, 이 변수가 우리가 프로덕트 환경에서 build 명령어를 수행했는지를 판단하고 우리가 수행한 build라면 캐싱된 데이터를 반환하고,
ISR로 인해 build가 된다면 매번 새로운 posts 데이터를 받아올 수 있게 조건부 처리를 진행한거죠.
 
위처럼 ISR과 우리가 build하는 환경변수 값을 토대로 캐싱된 데이터를 사용할지, 아니면 매번 새로운 데이터를 사용할지를 구분할 수 있게 된 것입니다.
이 처럼… 이제 저는 notion에 글을 쓰면 제가 설정한 시간에 따라 유저에게 신선한 게시글을 제공할 수 있는거죠!
어떻게 보면 매우 쉬운 접근이였지만.. 이걸 이제와서 한다는게 참.. 어찌되었든 ISR 작업을 통해 좀 더 유연하게 되었으니 그거로 만족합니다!
감사합니다.