포스팅 1,000개 넘자 마주한 Cloudflare Pages 빌드 제한, SSG 블로그가 이를 극복한 방법
티스토리나 워드프레스를 떠나 정적 사이트 생성기(SSG)와 Cloudflare Pages 조합으로 블로그를 이전했을 때의 해방감은 대단합니다. 압도적인 페이지 로딩 속도, 그리고 무엇보다 ’트래픽 무제한 무료’라는 메리트는 1인 창작자에게 축복과도 같습니다.
하지만 블로그가 성장하여 포스팅 개수가 1,000개를 넘어서는 순간, 평화롭던 블로그 운영에 거대한 장벽이 가로막아 섭니다. 바로 Cloudflare Pages의 **‘빌드 시간 제한(20분)’**과 **‘월 빌드 횟수 제한(Free 플랜 기준 500회)’**입니다.
글이 늘어날수록 마크다운 변환, 이미지 최적화, 전체 페이지 인덱싱에 걸리는 시간이 기하급수적으로 늘어납니다. 결국 어느 날 갑자기 Build failed: command timed out이라는 절망적인 에러 메시지를 마주하게 되죠.
이 글에서는 SSG 블로그가 포스팅 1,000개를 돌파하면서 마주하게 되는 빌드 실패 문제를 분석하고, 요금제 업그레이드 없이 무료로 클라우드플레어 빌드 제한을 우회하는 기술적 솔루션을 공유합니다.
1. 포스팅 1,000개 돌파, 무엇이 빌드 실패를 유발하는가?
정적 사이트 빌드 프로세스는 포스팅 개수에 비례하여 무거워집니다. 특히 Gatsby, Next.js, Astro 같은 모던 프레임워크나 Hugo, Jekyll 같은 전통적인 SSG 도구 모두 다음과 같은 병목 구간을 지니고 있습니다.
- 마크다운(Markdown) AST 파싱: 수천 개의 .md 혹은 .mdx 파일을 읽어 들여 HTML로 변환하고 구문 강조(Syntax Highlighting)를 적용하는 과정에서 상당한 CPU 자원이 소모됩니다.
- 이미지 프로세싱(가장 큰 원인): 반응형 웹을 위해 하나의 원본 이미지를 AVIF, WebP 등 다양한 크기와 포맷으로 변환(Resize)하는 작업은 엄청난 메모리와 시간을 잡아먹습니다.
- 의존성 설치 및 캐시 부재: Cloudflare Pages의 자체 빌드 환경은 매번 가상 환경을 컨테이너 기반으로 띄우기 때문에, 종종 빌드 캐시가 제대로 작동하지 않아 NPM 패키지를 매번 새로 설치해야 하는 비효율이 발생합니다.
포스팅이 1,000개를 넘고 이미지가 수천 장 쌓인 블로그라면, 이 문제는 단지 우려가 아닌 현실입니다. 정확한 빌드 소요 시간과 실패 빈도는 콘텐츠 구성과 이미지 용량에 따라 크게 달라지므로, 일반화된 수치를 단정하기보다 Cloudflare Pages 대시보드의 빌드 로그에서 직접 확인해 보는 것이 정확합니다.
2. 해결책의 핵심: 클라우드플레어 빌드 제한 우회 아키텍처
클라우드플레어의 빌드 제한을 우회하는 가장 똑똑하고 합법적인 방법은 **“빌드는 성능이 더 좋고 유연한 곳에서 수행하고, Cloudflare Pages는 단순히 결과물(HTML/CSS/JS)을 호스팅하는 용도로만 사용한다”**는 접근입니다.
이를 가능하게 하는 구체적인 방법이 바로 GitHub Actions와 Wrangler CLI Direct Deploy의 조합입니다.
[기존 방식]
GitHub Commit ──> Cloudflare Pages (빌드 엔진 구동: 20분 제한 걸림) ──> 배포 완료
[우회 방식]
GitHub Commit ──> GitHub Actions (강력한 가상 머신에서 빌드 및 캐싱)
└──> Wrangler CLI를 통해 정적 파일 직접 업로드 ──> 배포 완료 (1분 내외)
이 구조를 채택하면 다음과 같은 이점을 즉시 얻을 수 있습니다.
- 시간 제한 우회: GitHub Actions는 무료 플랜에서도 단일 빌드에 최대 6시간까지 허용합니다. Cloudflare의 20분 제한은 남의 나라 이야기가 됩니다.
- 빌드 횟수 우회: Cloudflare의 빌드 컨테이너를 쓰지 않고 정적 파일만 업로드(Direct Upload)하기 때문에, Cloudflare Pages 기준의 ‘월 빌드 횟수’ 카운트가 차감되지 않습니다. (완벽한 우회)
- 압도적인 캐싱 성능: GitHub의
actions/cache를 활용하여 이미지 최적화 결과물과 프레임워크 캐시를 완벽하게 재사용할 수 있습니다.
3. GitHub Actions를 활용한 우회 배포 구축 단계별 가이드
이제 구체적인 전환 방법을 알아보겠습니다. 이 가이드는 Cloudflare Pages와 GitHub 리포지토리가 이미 연동되어 있는 상태를 전제로 합니다.
1단계: Cloudflare Pages 자동 빌드 비활성화
우선 Cloudflare 대시보드에 접속하여, Git 커밋 시마다 Cloudflare가 스스로 빌드를 시작하던 기존 설정을 꺼야 합니다.
- Cloudflare Pages 프로젝트 대시보드 진입 -> 설정(Settings) -> **빌드 및 배포(Builds & deployments)**로 이동합니다.
- 자동 빌드 배포(Automatic deployments) 항목에서 활성화된 브랜치 배포를 ‘비활성화(Pause)’ 처리합니다.
2단계: API 토큰 및 계정 ID 발급
GitHub Actions가 내 Cloudflare 계정에 빌드 결과물을 강제로 밀어 넣을 수 있도록 권한을 부여해야 합니다.
- Cloudflare 대시보드 우측 상단의 프로필 클릭 -> My Profile -> API Tokens로 이동합니다.
- Create Token을 누르고
Cloudflare Pages - Edit템플릿을 선택하거나, Account 수준의 Cloudflare Pages 편집 권한을 가진 토큰을 생성합니다. - 내 Cloudflare Pages 주소 표시줄 또는 계정 홈에서 Account ID를 복사해 둡니다.
3단계: GitHub Secrets 설정
배포 대상 리포지토리의 Settings -> Secrets and variables -> Actions로 이동하여 다음 두 가지 보안 변수를 추가합니다.
CLOUDFLARE_API_TOKEN: 방금 생성한 API 토큰 값CLOUDFLARE_ACCOUNT_ID: 복사해 둔 계정 ID 값
4단계: 배포 워크플로우 파일(.github/workflows/deploy.yml) 생성
프로젝트 루트 디렉토리에 .github/workflows/deploy.yml 파일을 만들고 아래 코드를 붙여넣습니다. (아래 예시는 Next.js/Gatsby 등 NPM 기반 SSG 기준이며, Hugo 등은 빌드 커맨드만 변경하면 됩니다.)
name: Deploy to Cloudflare Pages
on:
push:
branches:
- main # 혹은 배포 기준 브랜치명
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # 패키지 캐싱 활성화
- name: Install dependencies
run: npm ci
# 빌드 속도 극대화를 위한 이전 빌드 캐시 복원 (예: Next.js 캐시)
- name: Restore Build Cache
uses: actions/cache@v4
with:
path: |
.next/cache
.cache # Gatsby의 경우
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx', '**/posts/**/*.md') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- name: Build Project
run: npm run build # SSG 정적 파일 생성 명령어 (출력 폴더 예: out, public, dist 등)
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: 'your-cloudflare-project-name' # 클라우드플레어에 등록된 프로젝트명 입력
directory: 'out' # 빌드 완료된 정적 파일이 저장되는 폴더명 (SSG 프레임워크 사양에 맞출 것)
branch: 'main'
4. 빌드 방식 전환 전후 성능 및 비용 비교
이와 같이 빌드 파이프라인을 완전히 분리하면, 유료 플랜으로 업그레이드하지 않고도 빌드 안정성과 속도를 모두 챙길 수 있습니다.
| 비교 항목 | 기존 Cloudflare 자체 빌드 | GitHub Actions 우회 배포 |
|---|---|---|
| 빌드 시간 제한 | 최대 20분 (초과 시 실패) | 최대 360분 (6시간) |
| 월별 빌드 횟수 한도 | Free 플랜 기준 월 500회 제한 | GitHub 무료 기준 월 2,000분 (빌드 횟수 자체가 카운트되지 않음) |
| 의존성 캐싱 | 불안정함 (매번 클린 인스톨 빈번) | actions/cache로 완벽한 캐시 제어 |
| 월간 유지 비용 | Pro 업그레이드 시 $20/월 | $0 (완전 무료) |
실제 평균 빌드 속도와 절감되는 비용은 포스팅 수, 이미지 용량, 캐시 적중률에 따라 달라지므로, 전환 전후의 Actions 실행 로그를 직접 비교해 보는 것이 가장 정확합니다.
5. 추가 꿀팁: 이미지 최적화 병목 해결하기
빌드 서버를 옮겼더라도 1,000개가 넘는 글 속 이미지 수천 장을 빌드할 때마다 매번 다시 리사이징한다면 GitHub Actions의 무료 제공 시간(월 2,000분)도 금방 바닥나게 됩니다. 이 단계까지 오셨다면 다음 두 가지 추가 최적화를 강력히 권장합니다.
- 원격 이미지 호스팅 분리 (추천): 이미지를 프로젝트 내부 폴더(
assets/images)에 두고 빌드 시점에 변환하는 대신, Cloudflare Images나 AWS S3 + Imgix 같은 별도의 이미지 CDN 서비스로 주소를 이관하세요. 빌드 시점의 이미지 프로세싱 작업 자체가 완전히 사라지므로 빌드 시간이 1~2분 안팎으로 단축됩니다. - 로컬 사전 변환 스크립트 구축: 굳이 로컬 환경에 이미지를 두고 배포해야 한다면, 웹 서버 빌드 과정에서 이미지를 최적화하지 말고, 깃에 커밋하기 전에 미리 로컬에서 WebP/AVIF로 일괄 변환해 두는 Git Hook 스크립트를 작성하여 커밋하는 것이 좋습니다.
정적 사이트의 성장은 블로그의 가치가 높아짐을 뜻하는 기분 좋은 지표입니다. 하지만 플랫폼의 제약에 가로막혀 매달 수십 달러의 비용 지출을 강요받거나, 빌드가 실패할까 봐 전전긍긍하며 글 쓰기를 주저한다면 이는 주객전도가 아닐 수 없습니다.
이번 포스팅에서 다룬 GitHub Actions와 Wrangler CLI를 통한 빌드 우회 전략은 단순히 한계를 회피하는 꼼수가 아닙니다. 클라우드 인프라를 가장 효율적으로 쪼개어 쓰는 지극히 현대적이고 표준적인 엔지니어링 설계 기법입니다. 이제 빌드 에러에 대한 스트레스는 기계에게 완전히 맡겨두고, 우리는 더 양질의 콘텐츠를 생산하는 일에만 집중할 때입니다.