Cloudflare배포Next.js

Cloudflare Pages 배포 시 빌드 캐시 이슈와 해결기

2026.04.20

빌드 캐시가 적중하거나 빗나갈 때의 빌드 시간 차이를 나타낸 일러스트

빌드가 5분에서 8분으로 늘어났다

Cloudflare Pages에 Next.js 블로그를 배포한 직후에는 빌드가 3~5분이었다. 글이 30개로 늘어났을 때 8분으로 늘어났다. 정상이라기엔 너무 느렸다.

빌드 로그를 뒤져보니 매번 npm install이 처음부터 돌고 있었다. 빌드 캐시가 동작 안 하는 상태였다. 캐시만 잡아주면 5분이 다시 1~2분으로 줄어든다.

이 글은 Cloudflare Pages에서 빌드 캐시 관련해 부딪힌 4가지 이슈와 해결 방법이다.

이슈 1: 매 빌드마다 npm install이 처음부터 돈다

가장 큰 문제. Cloudflare Pages는 기본적으로 node_modules를 캐시한다고 알려져 있지만, 실제로는 일정 조건이 안 맞으면 캐시가 안 된다.

확인 방법: 빌드 로그에서 Restoring cached dependencies 또는 Using cached node_modules 같은 줄을 찾는다. 없으면 캐시 미스다.

원인 1: 빌드 환경 변수 누락

Cloudflare Pages는 NODE_VERSION 등 환경 변수가 빌드마다 일관되어야 캐시를 적용한다. 대시보드 → Settings → Environment Variables에서 명시적으로 박아둔다.

NODE_VERSION=22
NPM_VERSION=10

이게 없으면 매 빌드마다 노드 버전이 약간 달라지면서 캐시가 깨진다.

원인 2: package-lock.json이 git에 없음

package-lock.json(또는 pnpm-lock.yaml, yarn.lock)이 git에 없으면 Cloudflare는 매번 새로운 의존성 트리로 본다. 캐시가 작동하지 않는다.

# .gitignore에서 lock 파일이 ignore되고 있지 않은지 확인
git check-ignore package-lock.json   # 출력이 있으면 ignore 중

ignore 중이면 빼고 커밋한다.

이슈 2: 빌드 출력 캐싱이 안 된다

.next/ 디렉토리는 Next.js의 빌드 산출물이다. Cloudflare Pages가 이걸 자동으로 캐시하지는 않는다. 그래서 매번 처음부터 빌드한다.

해결: wrangler.toml 또는 빌드 명령에서 캐시 위치를 명시한다.

# Build command
npm ci && npm run build && npx @cloudflare/next-on-pages

npm ci는 package-lock.json을 그대로 따른다. npm install보다 더 결정적이라 캐시 친화적이다.

이슈 3: _next 정적 자산 캐시가 적용 안 된다

배포는 끝났는데, 사용자가 캐싱된 옛 버전을 본다. _next/static/chunks/... 같은 파일이 브라우저 캐시에서 안 갱신된다.

이건 빌드 캐시가 아니라 브라우저/엣지 캐시 문제다. 헤더로 해결한다.

public/_headers 파일을 만든다.

/_next/static/*
  Cache-Control: public, max-age=31536000, immutable
 
/*.html
  Cache-Control: public, max-age=0, must-revalidate

_next/static/ 아래 파일은 해시가 들어 있어서 영구 캐시 가능. HTML은 매번 새로 받게 한다.

이 두 줄로 정적 자산은 빠르게, HTML은 항상 최신으로 동작한다.

이슈 4: 캐시를 강제로 비워야 할 때

Next.js 버전을 올렸거나, 빌드 도구를 바꿨거나, 캐시 자체에 뭔가 깨진 상태가 박혔을 때. Cloudflare Pages 대시보드에서 직접 비울 수 있다.

대시보드 → Pages 프로젝트 → Settings → Builds & deployments → "Clear build cache".

또는 빌드 명령 앞에 캐시 무효화 신호를 박는다.

# Build command
rm -rf .next node_modules/.cache && npm ci && npm run build

평소에는 이렇게 안 한다. 빌드 시간이 다시 길어진다. 캐시가 망가졌을 때만 임시로.

빌드 시간 단축 팁

캐시 외에도 빌드 자체를 줄이는 방법들이 있다.

1. npm ci 대신 pnpm install

pnpm은 의존성 설치가 npm보다 2~3배 빠르다.

# Build command
pnpm install --frozen-lockfile && pnpm build && npx @cloudflare/next-on-pages

대시보드에서 PNPM_VERSION 환경 변수만 박으면 된다.

2. 빌드 시 사용 안 하는 의존성 제거

devDependencies에 무거운 패키지가 많으면 빌드 시간이 늘어난다. 사용 안 하는 거 정리.

npx depcheck

depcheck가 사용 안 하는 의존성을 찾아준다.

3. MDX 빌드 결과 캐시

콘텐츠가 많아지면 MDX 파싱이 빌드 시간의 큰 부분이 된다. 빌드 결과를 캐시하면 변경된 파일만 다시 처리한다.

내가 쓰는 패턴:

// scripts/generate-content-index.ts
import fs from "node:fs";
import matter from "gray-matter";
 
const cache = JSON.parse(fs.readFileSync(".cache/posts.json", "utf-8") || "{}");
 
for (const file of fs.readdirSync("content/blog")) {
  const stat = fs.statSync(`content/blog/${file}`);
  if (cache[file]?.mtime === stat.mtimeMs) continue;  // 캐시 히트
  // 파싱 후 캐시에 저장
  cache[file] = { mtime: stat.mtimeMs, ...matter(...).data };
}
 
fs.writeFileSync(".cache/posts.json", JSON.stringify(cache));

.cache/를 git에 올리지는 않지만, 이게 Cloudflare 빌드 캐시에 들어가면 효과를 본다.

흔한 함정

함정 1: 환경 변수 변경마다 캐시가 깨진다

빌드에서 사용하는 환경 변수가 바뀌면 Cloudflare는 캐시를 무효화한다. 자주 바뀌는 값(예: 빌드 ID)을 환경 변수로 박지 마라. 빌드 시 동적으로 생성한다.

함정 2: wrangler.toml만 믿지 마라

이전 글에서 다룬 nodejs_compat 이슈처럼, wrangler.toml 설정의 일부는 Pages가 안 읽는다. 빌드 관련 설정도 마찬가지로 대시보드에서 직접 박는 게 안전하다.

함정 3: 프리뷰 환경과 프로덕션 캐시가 분리

Cloudflare Pages는 PR 프리뷰 빌드와 프로덕션 빌드의 캐시가 분리되어 있다. 한쪽에서 캐시가 잘 돼도 다른 쪽은 별개다. 프리뷰가 빠르다고 프로덕션도 빠를 거라 가정하지 마라.

직접 겪은 일: 로컬 빌드가 통과해도 배포가 깨진다

캐시와는 별개로, Cloudflare에 올리면서 제일 많이 데인 건 런타임이었다.

새 API 라우트나 서버 컴포넌트 페이지를 만들 때마다 이 한 줄을 붙여야 한다.

export const runtime = "edge";

빠뜨리면 Node 런타임으로 잡히고, Cloudflare에서는 그대로 터진다. 문제는 로컬 npm run build가 멀쩡히 통과한다는 것이다. 로컬에는 Node가 있으니 당연히 통과한다. 그래서 빌드 초록불만 보고 올렸다가, 배포된 사이트를 열어보고서야 알게 된다.

지금 이 사이트에서 edge를 붙이고 있는 곳은 네 군데다. 어드민 페이지와 그 레이아웃, 인증 콜백 라우트, 그리고 날씨 API 라우트.

빌드가 잡아주지 않는 종류의 실수라서, 라우트를 새로 만들 때 확인하는 항목으로 따로 적어두는 편이 낫다. 캐시 문제는 로그를 보면 원인이 보이지만 이건 로그도 안 남는다.

정리

Cloudflare Pages 빌드 캐시는 자동이지만, 일정 조건이 안 맞으면 동작 안 한다. 4가지를 한 줄씩 정리한다.

  1. NODE_VERSION 환경 변수 명시
  2. package-lock.json을 git에 포함
  3. _headers로 정적 자산 영구 캐싱
  4. 빌드가 깨진 것 같으면 대시보드에서 캐시 비우기

추가로 pnpm 도입과 depcheck로 의존성 정리만 해도 빌드 시간이 다시 절반 이하로 떨어진다.

내 블로그는 이 셋업으로 빌드 시간이 8분 → 1분 30초로 줄었다. 글 하나 쓰고 푸시한 후 라이브 반영까지 2분이면 끝난다. 빌드가 빠르면 글을 더 자주 쓰게 된다는 부수 효과도 있다.

같이 읽으면 좋은 글