Vercel Hobby 플랜을 쓰다가 사이트에 애드센스를 붙였다면, 그 순간부터 약관 위반입니다. 용량을 초과해서가 아니라 조항 자체가 그렇습니다.
저는 한도 걱정을 하다가 이걸 알았습니다. 한도는 아직 한참 남아 있었는데, 정작 문제는 다른 데 있었습니다.
이사 트리거는 용량이 아니라 약관이었습니다
공식 Fair Use 문서의 문장입니다.
Hobby teams are restricted to non-commercial personal use only. All commercial usage of the platform requires either a Pro or Enterprise plan.
여기까지는 알려진 얘기입니다. 문제는 “상업적 사용”의 정의가 생각보다 훨씬 넓다는 겁니다.
any Deployment that is used for the purpose of financial gain of anyone involved in any part of the production of the project, including a paid employee or consultant writing the code
프로젝트 제작에 관여한 누구든 금전적 이득을 얻으면 해당됩니다. 문서가 직접 드는 예시는 이렇습니다.
- 방문자에게 결제를 요청하거나 처리하는 모든 방식
- 제품이나 서비스의 판매를 광고하는 것
- 사이트를 만들거나 수정하거나 호스팅하는 대가를 받는 것
- 제휴 링크가 사이트의 주목적인 경우
- 구글 애드센스를 포함한 광고 게재
마지막 줄에 개인 블로그 상당수가 걸립니다. 애드센스 한 줄이면 끝입니다. 반대로 기부 요청은 상업적 사용이 아닙니다. 문서에 명시돼 있습니다.
외주로 만들어준 사이트를 Hobby에 올려둔 경우도 걸립니다. 사이트가 돈을 못 벌어도 코드를 쓴 사람이 돈을 받았으면 해당되니까요.
Hobby 플랜 실제 한도
참고로 한도는 이렇습니다. 대부분은 여기에 걸리기 전에 위 조항에 먼저 걸립니다.
| 항목 | Hobby |
|---|---|
| Fast Data Transfer | 100 GB |
| Fast Origin Transfer | 10 GB |
| Function Invocations | 1M |
| Active CPU | 4 CPU-hrs |
| Provisioned Memory | 360 GB-hrs |
| 이미지 변환 | 5K/월 |
옮길 가치가 있는 프로젝트, 없는 프로젝트
전부 옮길 필요는 없습니다. 기준은 단순합니다.
옮길 가치가 있는 것 — 광고나 결제가 붙은 사이트, 외주 결과물, 정적 사이트에 가까운 블로그·랜딩페이지. 특히 정적이면 이사에 손댈 게 거의 없습니다. 이 블로그도 Astro 정적 빌드라 Cloudflare Pages에 올리는 데 10분 걸렸습니다.
남겨둘 것 — 순수 토이 프로젝트, ISR과 이미지 최적화에 깊이 기댄 앱, Next.js 최신 기능을 계속 따라가야 하는 앱. 뒤에 나올 호환성 문제를 감수할 이유가 없습니다.
2026년 9월, 배포 경로가 또 바뀌었습니다
여기가 이 글에서 제일 중요한 부분입니다. 검색해서 나오는 마이그레이션 글은 대부분 @opennextjs/cloudflare를 쓰라고 합니다. 그 전 세대 글은 @cloudflare/next-on-pages를 쓰라고 하고요. 지금은 둘 다 최신 권장안이 아닙니다.
Cloudflare의 OpenNext 문서 맨 위에 이렇게 적혀 있습니다.
Cloudflare recommends vinext instead of OpenNext for new Next.js applications on Cloudflare Workers.
OpenNext 페이지는 이제 기존 앱을 유지보수하는 사람을 위한 문서입니다. 새 프로젝트의 기본 경로는 vinext입니다. Next.js API를 Vite 위에서 다시 구현한 물건이고 Next.js 16 API 표면의 약 94%를 덮습니다.
그런데 저장소 경고를 읽어보면 바로 결정이 섭니다.
Under active development. vinext supports substantial Next.js applications today, but it is not yet a drop-in replacement for every application or production workload. Expect compatibility gaps, especially in newer App Router features.
Next.js 16.x에서만 돌아가고, Vercel 전용 기능과 next export, Turbopack/webpack 설정, next/jest는 아예 지원하지 않습니다.
그래서 제 결론은 이렇습니다. 지금 돌아가는 앱을 옮기는 거라면 OpenNext입니다. 권장에서 밀려났을 뿐 문서와 경로가 살아 있고, 깨지는 지점이 이미 알려져 있습니다. vinext는 Next.js 16을 쓰고 있고 호환성 구멍을 직접 확인할 여유가 있을 때 고르세요. “공식 권장”이라는 말만 보고 프로덕션 앱을 먼저 올리면 안 됩니다.
실제로 깨진 것들
경로를 골랐으면 이제 깨집니다. 자주 나오는 순서대로입니다.
Edge Runtime을 강제하는 어댑터에서는 이게 먼저 뜹니다.
Error: Dynamic Code Evaluation (e.g. 'eval', 'new Function', 'WebAssembly.compile') is not allowed in Edge Runtime.
의존성 하나가 내부에서 eval을 쓰면 걸립니다. 내 코드가 아니라 남의 패키지라 고칠 수도 없습니다.
Node 내장 모듈이 없어서 나는 것도 같은 뿌리입니다.
[Error] Cannot find module 'crypto' imported from '/functions/api/auth/token.func.js'
둘 다 nodejs_compat으로 해결합니다. 이게 빠지면 아래 에러가 따라옵니다.
TypeError: Cannot read properties of undefined (reading 'MY_VAR')
compatibility_date가 2025년 4월 이전이면 process.env가 자동으로 채워지지 않습니다. 환경변수를 제대로 넣었는데 undefined가 나오면 여기를 보세요.
설정은 결국 이 두 줄입니다.
compatibility_date = "2026-09-14"
compatibility_flags = ["nodejs_compat"]
compatibility_date는 2024-09-23 이후여야 하고, 공식 문서는 그냥 오늘 날짜를 넣으라고 합니다.
옮기고 나서 잃은 것
정직하게 씁니다. Vercel이 편했던 것들이 그냥 없어집니다.
ISR, 이미지 최적화, PR마다 자동으로 생기는 프리뷰 배포. 특히 next/image에 기대고 있었다면 대체재를 직접 붙여야 합니다.
작지만 성가신 것도 있습니다. next.config.ts의 headers()로 보안 헤더를 걸어뒀다면, 정적으로 내보내는 순간 그게 안 따라옵니다. Cloudflare에서는 public/_headers 파일로 다시 써야 합니다. 이 블로그도 그렇게 합니다.
비용
| Vercel | Cloudflare Workers | |
|---|---|---|
| 무료 | Hobby (비상업용만) | 10만 요청/일 |
| 유료 시작 | Pro $20/월 (좌석 1개 포함) | $5/월 |
| 유료 포함량 | Fast Data Transfer 1 TB, Edge Request 1,000만 | 1,000만 요청/월 |
| 전송량 과금 | 있음 | 없음 |
| 정적 자산 | 전송량에 포함 | 무료·무제한 |
상업적 사용을 하려면 Vercel은 Pro가 강제입니다. Cloudflare는 무료 플랜에 상업적 사용 제한이 없고 egress 과금도 없습니다. 정적 자산 비중이 큰 사이트일수록 차이가 벌어집니다.
같은 계산을 DB 쪽에도 적용한 글이 Supabase 무료 티어 한계이고, 이 구성을 어떻게 짰는지는 1인 개발자 최소 인프라에 있습니다.
