Cloudflare Pages→Workers 마이그레이션 가이드

2025년 말부터 Cloudflare는 공식적으로 Pages를 유지보수 모드(maintenance mode)로 전환하고, 향후 모든 투자와 신규 기능을 Workers 플랫폼에 집중하겠다고 선언했다. 기존 Pages 프로젝트는 계속 작동하지만, Cron Triggers, 향상된 Observability, Durable Objects 직접 바인딩 같은 신규 기능은 Workers에서만 제공된다.
이 글은 Cloudflare Pages에서 운영 중인 정적 사이트·풀스택 앱을 Workers Static Assets로 무중단 마이그레이션하는 전체 절차를 다룬다. 실제로 이 블로그(effidev.dev)가 현재 pages_build_output_dir: "dist"로 운영 중인 Pages 프로젝트이므로, 동일한 마이그레이션을 수행하는 관점에서 작성했다.
핵심 요약
- Pages는 유지보수 모드: 신규 기능·최적화 투자가 중단되었다. Workers가 정적 자산 + 동적 로직을 단일 배포 단위로 서비스하는 공식 권장 방식이다.
- 정적 자산 요청 무료·무제한: Workers Static Assets를 통해 서빙되는 정적 파일(HTML, CSS, 이미지)은 Worker 호출(invocation)이 아니므로 요금이 발생하지 않는다.
- 파일 한도 10만 개: 유료 플랜 기준 Worker 버전당 최대 100,000 파일, 개별 파일 25MiB까지 지원한다(Wrangler 4.34.0+).
wrangler.jsonc한 줄 변경:pages_build_output_dir을assets.directory로 교체하면 핵심 전환이 완료된다.- 프레임워크 어댑터 통합: Astro, Next.js, SvelteKit 등 SSR 프레임워크는 Cloudflare Workers 호환 어댑터만 설정하면 정적 자산과 서버사이드 로직이 하나의 Worker로 배포된다.
1. Pages vs Workers Static Assets: 무엇이 달라지는가
Cloudflare 공식 마이그레이션 가이드에서 명시하는 핵심 차이다.
| 비교 항목 | Cloudflare Pages | Workers Static Assets |
|---|---|---|
| 플랫폼 상태 | 유지보수 모드 (신규 기능 중단) | 활발한 개발 + 투자 집중 |
| 정적 자산 서빙 | 내장 | assets.directory 설정으로 동일 지원 |
| 서버사이드 로직 | _worker.js (제한적) |
풀 Worker 스크립트 (무제한 바인딩) |
| Durable Objects | 직접 바인딩 불가 | 직접 바인딩 가능 |
| Cron Triggers | 미지원 | 지원 |
| Observability | 기본 로그만 | Workers Logs, Tail Workers, Logpush |
| 파일 한도 | 20,000개 | 100,000개 (유료 플랜) |
| 배포 명령 | wrangler pages deploy |
wrangler deploy |
| 정적 자산 요금 | 무료 | 무료 (Worker 호출 아님) |
결정적 차이는 Durable Objects와 Cron Triggers의 직접 바인딩이다. Pages에서는 이를 별도의 Worker로 분리해야 했지만, Workers Static Assets에서는 하나의 wrangler.jsonc에서 정적 자산 서빙 + DO + Cron + D1 + R2를 모두 설정할 수 있다.
2. 마이그레이션 전 체크리스트
전환 전에 확인해야 할 항목이다. 하나라도 미충족이면 마이그레이션 후 장애가 발생한다.
| 체크리스트 | 확인 방법 |
|---|---|
| Wrangler 버전 ≥ 4.34.0 | npx wrangler --version |
| 빌드 출력 디렉토리 확인 | ls dist/ 또는 프레임워크별 빌드 출력 경로 |
_worker.js 사용 여부 |
Pages advanced mode에서 사용 시 Worker 스크립트로 전환 필요 |
| 커스텀 도메인 DNS 설정 | Pages 프로젝트의 CNAME 레코드를 Workers 라우트로 재설정 |
_headers / _redirects 파일 |
Workers에서는 동작하지 않음 → Worker 스크립트 내 로직으로 이전 |
| 환경 변수 / Secrets | Pages 대시보드 → wrangler.jsonc의 [vars] 또는 wrangler secret put |
# 현재 wrangler 버전 확인
npx wrangler --version
# → 4.34.0 이상이어야 100,000 파일 한도 지원
# Pages 프로젝트의 현재 설정 확인
cat wrangler.jsonc
3. 핵심 마이그레이션: wrangler.jsonc 변환
기존 Pages 설정에서 Workers Static Assets로 전환하는 최소 변경 diff이다.
// wrangler.jsonc
{
"name": "effidev",
"compatibility_date": "2026-08-04",
- "pages_build_output_dir": "dist"
+ "main": "src/worker.ts",
+ "assets": {
+ "directory": "./dist",
+ "binding": "ASSETS"
+ }
}
이 변경으로 기존에 wrangler pages deploy dist로 배포하던 것이 wrangler deploy로 바뀐다. 정적 자산은 ./dist 디렉토리에서 자동으로 업로드되며, Worker 스크립트(src/worker.ts)에서 동적 로직을 처리한다.
Worker 스크립트 (src/worker.ts)
정적 사이트만 서빙하는 가장 간단한 Worker 진입점이다.
// src/worker.ts
// 정적 자산만 서빙하는 최소 Worker — 동적 로직이 없으면 이것만으로 충분하다
export default {
async fetch(
request: Request,
env: { ASSETS: Fetcher },
): Promise<Response> {
// 모든 요청을 정적 자산 바인딩으로 전달
return env.ASSETS.fetch(request);
},
};
함정 주의: assets.run_worker_first를 true로 설정하면 모든 요청이 Worker 스크립트를 거쳐 Worker invocation 요금이 부과된다. 정적 사이트는 기본값(false)을 유지해야 한다 — 이 경우 정적 자산 요청은 Worker를 건너뛰고 직접 서빙되어 무료다.
4. _headers·_redirects → Worker 스크립트 이전
Pages에서 사용하던 _headers와 _redirects 파일은 Workers에서 동작하지 않는다. Worker 스크립트 내에서 직접 처리해야 한다.
// src/worker.ts — 헤더·리다이렉트 통합 버전
export default {
async fetch(
request: Request,
env: { ASSETS: Fetcher },
): Promise<Response> {
const url = new URL(request.url);
// _redirects 대체: 이전 URL 패턴을 새 경로로 301 리다이렉트
const redirects: Record<string, string> = {
'/old-blog/': '/ko/blog/',
'/legacy-page/': '/ko/',
};
const redirect = redirects[url.pathname];
if (redirect) {
return Response.redirect(new URL(redirect, request.url).toString(), 301);
}
// 정적 자산 가져오기
const response = await env.ASSETS.fetch(request);
// _headers 대체: 응답 헤더 커스터마이징
const headers = new Headers(response.headers);
// 보안 헤더 추가
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('X-Frame-Options', 'DENY');
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// 캐시 정책: 이미지는 1년, HTML은 10분
if (url.pathname.match(/\.(webp|png|jpg|svg|woff2)$/)) {
headers.set('Cache-Control', 'public, max-age=31536000, immutable');
} else if (url.pathname.endsWith('.html') || url.pathname.endsWith('/')) {
headers.set('Cache-Control', 'public, max-age=600, s-maxage=3600');
}
return new Response(response.body, {
status: response.status,
headers,
});
},
};
이 Worker에서는 assets.run_worker_first를 true로 설정해야 한다 — 헤더 조작이 필요하기 때문이다. 대신 Worker invocation 요금이 발생하지만, Paid 플랜에서는 1,000만 요청/월이 $5에 포함되므로 대부분의 정적 사이트에서는 충분하다.
// wrangler.jsonc — run_worker_first 활성화
{
"name": "effidev",
"compatibility_date": "2026-08-04",
"main": "src/worker.ts",
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": true
}
}
5. SSR 프레임워크 어댑터 설정 (Astro·Next.js·SvelteKit)
이 블로그처럼 Astro를 사용하는 정적 사이트는 별도 어댑터 없이 빌드 출력만 assets.directory로 연결하면 된다. 하지만 SSR을 사용하는 프레임워크라면 Cloudflare Workers 호환 어댑터가 필요하다.
Astro (정적 빌드 — 가장 간단)
# astro.config.mjs — 어댑터 없이 정적 빌드
# output: 'static' (기본값)이면 dist/에 HTML이 직접 생성된다
npm run build
# → dist/ 폴더를 wrangler.jsonc의 assets.directory로 지정
Astro (SSR 모드)
npx astro add cloudflare
// astro.config.mjs — Cloudflare Workers SSR 어댑터
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'server',
adapter: cloudflare({
platformProxy: { enabled: true },
}),
});
Next.js (opennextjs-cloudflare)
npx create-next-app@latest --typescript
npm install @opennextjs/cloudflare
Next.js 16 "use cache" RSC 스트리밍 가이드에서 다룬 "use cache" 디렉티브와 RSC 스트리밍도 Workers 어댑터를 통해 정상 동작한다.
6. Durable Objects·Cron·D1·R2 통합 배포
Pages에서 불가능했던 핵심 기능이다. Workers Static Assets로 전환하면 하나의 wrangler.jsonc에서 정적 사이트 + 백엔드 바인딩을 모두 관리할 수 있다.
// wrangler.jsonc — 풀스택 통합 설정
{
"name": "effidev",
"compatibility_date": "2026-08-04",
"main": "src/worker.ts",
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": true
},
// D1 데이터베이스
"d1_databases": [
{
"binding": "DB",
"database_name": "effidev-analytics",
"database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
],
// R2 오브젝트 스토리지
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "effidev-media"
}
],
// Cron Triggers (Pages에서는 불가능!)
"triggers": {
"crons": ["0 */6 * * *"]
}
}
AWS S3에서 Cloudflare R2로 마이그레이션한 가이드에서 다룬 R2 버킷 바인딩도 이제 별도 Worker 없이 블로그 Worker와 통합할 수 있다.
// src/worker.ts — Cron + D1 + R2 통합 핸들러
interface Env {
ASSETS: Fetcher;
DB: D1Database;
BUCKET: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// API 엔드포인트는 Worker가 직접 처리
if (url.pathname.startsWith('/api/')) {
return handleApi(request, env);
}
// 나머지는 정적 자산 서빙
return env.ASSETS.fetch(request);
},
// 6시간마다 실행되는 Cron (Pages에서는 별도 Worker가 필요했다!)
async scheduled(
_controller: ScheduledController,
env: Env,
): Promise<void> {
// D1에서 분석 데이터 집계
const result = await env.DB.prepare(
'SELECT COUNT(*) as total FROM page_views WHERE date = date("now")',
).first();
// R2에 일간 리포트 저장
await env.BUCKET.put(
`reports/${new Date().toISOString().slice(0, 10)}.json`,
JSON.stringify(result),
);
},
};
async function handleApi(request: Request, env: Env): Promise<Response> {
// API 로직 구현
return new Response(JSON.stringify({ status: 'ok' }), {
headers: { 'Content-Type': 'application/json' },
});
}
7. 배포 명령 변경과 DNS 전환
배포 명령
# Before (Pages)
npx wrangler pages deploy dist --project-name=effidev
# After (Workers Static Assets)
npx wrangler deploy
Workers 배포는 wrangler.jsonc의 name 필드를 프로젝트명으로 사용한다.
DNS 전환 (커스텀 도메인)
Pages 프로젝트의 커스텀 도메인은 CNAME으로 <project>.pages.dev를 가리킨다. Workers로 전환 시 Cloudflare 대시보드에서 Workers → 라우트(Route)로 도메인을 연결해야 한다.
| 단계 | 조치 |
|---|---|
| 1. Workers 배포 | wrangler deploy로 Worker + 정적 자산 배포 |
| 2. 커스텀 도메인 연결 | Cloudflare 대시보드 → Workers → 해당 Worker → Settings → Domains & Routes → Add Custom Domain |
| 3. Pages 커스텀 도메인 해제 | Pages 프로젝트에서 커스텀 도메인 제거 (DNS 충돌 방지) |
| 4. 검증 | `curl -sI https://yourdomain.com/ |
함정: DNS 전환 중 Pages와 Workers가 같은 도메인을 동시에 바인딩하면 라우팅 충돌이 발생한다. 반드시 Pages 도메인을 먼저 해제한 후 Workers 라우트를 추가한다.
8. .assetsignore로 불필요한 파일 제외
빌드 출력 디렉토리에 배포하지 않을 파일이 있으면 .assetsignore를 사용한다. .gitignore와 동일한 문법이다.
# dist/.assetsignore
_worker.js # Pages advanced mode 잔재 — Worker 자산으로 업로드되면 안 된다
*.map # 소스맵은 프로덕션에 올리지 않는다
.DS_Store
자주 묻는 질문
기존 Pages 프로젝트를 당장 마이그레이션해야 하나요?
아니다. 기존 Pages 프로젝트는 계속 작동한다. 다만 Cron Triggers, Durable Objects 직접 바인딩, Workers Logs 같은 신규 기능이 필요하거나, 파일 수가 20,000개를 초과하는 대규모 프로젝트라면 마이그레이션이 권장된다.
정적 사이트인데 Worker invocation 요금이 발생하나요?
assets.run_worker_first를 기본값(false)으로 유지하면 정적 자산 요청은 Worker를 거치지 않으므로 invocation 요금이 0이다. 헤더 조작이나 API 로직이 필요해서 true로 설정하더라도 Paid 플랜($5/월)에 1,000만 요청이 포함되어 있으므로 대부분의 정적 사이트에서는 추가 비용이 발생하지 않는다.
_headers와 _redirects 파일은 어떻게 되나요?
Workers에서는 동작하지 않는다. Worker 스크립트 내에서 직접 헤더 설정과 리다이렉트 로직을 구현해야 한다(§4 참조). 이는 코드로 관리되므로 오히려 버전 관리와 테스트가 용이해지는 장점이 있다.
Pages Functions(/functions/)를 쓰고 있었는데 어떻게 전환하나요?
Pages Functions의 각 파일을 Worker 스크립트의 라우팅 로직으로 통합한다. Workers에서는 단일 진입점(main)에서 URL 패턴에 따라 핸들러를 분기하는 구조다. Hono 같은 경량 라우터 프레임워크를 사용하면 기존 Functions 구조와 유사한 파일 기반 라우팅을 유지할 수 있다.