Next.js 14 빌드 메모리 초과(OOM) 완벽 해결: Fetch 캐시 최적화 아키텍처
[Troubleshooting] Next.js 14 빌드 메모리 초과(OOM) 완벽 해결: Fetch 캐시 최적화 아키텍처
"Next.js 14로 1만 개의 상품 페이지를 정적 생성(SSG)하도록 코드를 짜고 Vercel에 배포했는데, 빌드 과정 중 JavaScript heap out of memory (OOM) 에러를 뿜으며 서버가 뻗어버립니다."
React 기반의 Next.js App Router를 도입하여 대규모 커머스나 블로그를 구축하는 프론트엔드 엔지니어들이 필연적으로 마주하는 인프라 붕괴 현상입니다. 코드는 로컬 개발 환경(dev)에서 완벽하게 돌아갔지만, 프로덕션 빌드(build) 명령어를 치는 순간 Node.js의 물리적 메모리 한계를 초과해 버리는 것입니다.
초보자들은 이 에러를 보면 Node.js의 --max-old-space-size를 4GB, 8GB로 무작정 늘려버리는 원시적인 대응을 합니다. 하지만 데이터가 10만 개로 늘어나면 결국 또다시 터지게 됩니다. 본 가이드에서는 Next.js가 빌드 시점에 데이터를 메모리에 쥐고 놓지 않는 '공격적 캐싱'의 본질을 해체하고, 메모리 누수를 원천 차단하는 무결점 빌드 아키텍처를 단호하게 제시합니다.
📌 이 글의 핵심 포인트
- 에러의 본질:
generateStaticParams실행 시 수만 개의 fetch 응답이 메모리에 누적되는 Node.js 힙(Heap) 포화 현상- 공격적 캐시의 덫: Next.js 내장 Data Cache 메커니즘이 가비지 컬렉터(GC)의 메모리 반환을 막는 원리
- 아키텍처 최적화: GraphQL 또는 대용량 API 호출 시
cache: 'no-store'플래그를 활용한 메모리 다이어트 기법
1단계: 핵심 원인 분석 - 지워지지 않는 Fetch 캐시
Next.js 14는 극강의 성능을 내기 위해, 기본적으로 fetch() API를 통해 가져온 모든 데이터를 서버 메모리에 영구적으로 캐싱합니다. generateStaticParams 함수를 이용해 1만 개의 상품 ID를 가져와 각각의 상세 페이지 HTML을 구울 때, 내부적으로 1만 번의 fetch가 일어납니다.
문제는 Next.js가 "이 1만 개의 응답 데이터를 나중에 또 쓸지도 몰라"라며 메모리(Data Cache)에 끝까지 쥐고 있다는 점입니다. Node.js의 가비지 컬렉터(Garbage Collector)가 이를 청소하려 해도, 프레임워크가 강제로 참조하고 있으므로 청소할 수 없습니다. 결과적으로 메모리 점유율이 우상향 곡선을 그리며 임계점을 돌파해 OOM(Out of Memory) 에러로 사망하게 됩니다.
2단계: 완벽한 트러블슈팅 - 캐시 생명주기(Lifecycle) 통제
빌드 시점에 한 번만 쓰고 버려도 되는 일회성 대용량 데이터라면, 프레임워크에게 "이 데이터는 캐싱하지 말고 바로 메모리에서 버려라"라고 명시적으로 지시해야 합니다.
데이터 페칭 함수 내부의 fetch 옵션에 cache: 'no-store'를 주입하여 렌더링 의존성을 끊어내십시오.
// ❌ 최악의 안티 패턴 (메모리 폭발)
// Next.js가 이 API의 응답을 영원히 메모리에 캐싱하여 OOM 유발
async function getProductDetail(id) {
const res = await fetch(`https://api.example.com/products/${id}`);
return res.json();
}
// ✅ 무결점 아키텍처 (메모리 다이어트)
// 빌드 시점에 필요한 HTML만 구워낸 뒤, JSON 데이터는 메모리에서 즉각 폐기
async function getProductDetail(id) {
const res = await fetch(`https://api.example.com/products/${id}`, {
cache: 'no-store', // 또는 next: { revalidate: 0 }
});
return res.json();
}
이 사소한 옵션 하나를 추가하는 것만으로 1만 개의 페이지를 빌드할 때 점유하던 4GB의 메모리가 500MB 수준으로 극단적으로 압축됩니다.
3단계: 추가 엔터프라이즈 튜닝 - Worker Threads 분리
데이터 캐시를 껐음에도 이미지 최적화(next/image)나 무거운 컴포넌트 렌더링으로 인해 메모리가 튄다면, Next.js의 빌드 워커(Worker) 수를 제한하여 동시 실행되는 스레드를 통제해야 합니다. next.config.js에 experimental: { cpucpus: 1 } 옵션을 주어 빌드 속도는 조금 느려지더라도 메모리 초과를 완벽하게 방어하는 안정성을 취할 수 있습니다.
🙋♂️ 자주 묻는 질문 (FAQ)
Q. cache: 'no-store'를 쓰면 사용자가 접속할 때마다 API를 새로 찔러서 느려지지 않나요?
A. 아닙니다. 이 옵션은 Next.js 서버 내부의 'Data Cache'를 끄는 것일 뿐, 빌드된 결과물인 HTML과 정적 파일 자체는 정상적으로 생성(SSG)됩니다. 사용자는 이미 완성된 정적 HTML을 CDN을 통해 빛의 속도로 받게 되므로 성능 저하는 전혀 없습니다.
💡 핵심 정리 및 마무리
모든 데이터를 메모리에 우겨넣는 것은 아키텍처가 아니라 폭력입니다. Next.js가 제공하는 마법 같은 자동화 메커니즘 이면에는 반드시 통제해야 할 시스템 자원의 물리적 한계가 존재합니다. 렌더링과 캐싱의 생명주기를 완벽하게 해체하고 통제하여, 어떤 대용량 프로젝트에서도 멈추지 않는 무결점 빌드 파이프라인을 완성하시기 바랍니다.
댓글
댓글 쓰기