Cloudflare Pages
Cloudflare Pages란
Cloudflare Pages는 정적 사이트 + 서버리스 함수를 함께 호스팅하는 플랫폼이다. Vercel이나 Netlify와 비슷하지만 Cloudflare의 엣지 네트워크(300+ PoP) 위에서 동작한다.
- 정적 배포: HTML/CSS/JS 파일을 전 세계 CDN에 즉시 배포
- Pages Functions:
functions/폴더의 코드가 Workers로 자동 변환 — 백엔드 API를 같은 도메인에서 제공 - 무료 제한: 빌드 500회/월, 요청 10만 건/일 (무료 플랜)
- 미리보기 URL: 브랜치마다 고유 URL 자동 생성 (PR 검토에 활용)
Workers와 Pages의 차이는 아래와 같다.
| Workers | Pages | |
|---|---|---|
| 주 용도 | API 서버, 백엔드 로직 | 정적 사이트 + 가벼운 Functions |
| 진입점 | src/index.ts export | functions/ 폴더 파일 구조 |
| 배포 방식 | wrangler deploy | Git push 또는 wrangler pages deploy |
| D1 바인딩 | ✅ 완전 지원 | ✅ 지원 (설정 위치 다름) |
정적 사이트 배포
처음 연결 — GitHub/GitLab 연동
Cloudflare Dashboard → Workers & Pages → Create application → Pages → Connect to Git
→ 저장소 선택 → 빌드 명령·출력 디렉터리 설정
프레임워크별 기본값:
| 프레임워크 | 빌드 명령 | 출력 디렉터리 |
|---|---|---|
| Astro | npm run build | dist |
| Next.js | npm run build | .next |
| Vite (React/Vue) | npm run build | dist |
| SvelteKit | npm run build | build |
| 순수 HTML | (없음) | . 또는 public |
CLI로 배포
# 빌드 산출물을 Pages 프로젝트로 직접 업로드
npx wrangler pages deploy ./dist --project-name=my-site
# 처음 실행하면 대화형으로 프로젝트 이름 묻고 자동 생성
# wrangler.toml 없이도 동작 — 환경 변수만 따로 관리할 때는 Dashboard 사용
Pages Functions — 서버리스 API
functions/ 폴더의 파일이 URL 경로에 1:1 매핑된다.
functions/
api/
users.ts → GET/POST /api/users
users/
[id].ts → GET/PUT/DELETE /api/users/:id
_middleware.ts → 모든 요청에 적용되는 미들웨어
기본 함수 형태
// functions/api/hello.ts
import type { PagesFunction } from '@cloudflare/workers-types'
export const onRequestGet: PagesFunction = (ctx) => {
return Response.json({ message: 'Hello from Pages Function!' })
}
export const onRequestPost: PagesFunction = async (ctx) => {
const body = await ctx.request.json()
return Response.json({ received: body }, { status: 201 })
}
onRequest (모든 메서드), onRequestGet, onRequestPost, onRequestPut, onRequestDelete 로 메서드를 분리한다.
동적 경로 파라미터
// functions/api/users/[id].ts
import type { PagesFunction } from '@cloudflare/workers-types'
interface Env { DB: D1Database }
export const onRequestGet: PagesFunction<Env> = async ({ params, env }) => {
const user = await env.DB
.prepare('SELECT * FROM users WHERE id = ?')
.bind(params.id)
.first()
if (!user) return Response.json({ error: 'Not Found' }, { status: 404 })
return Response.json(user)
}
파일명의 [id] 가 params.id 로 전달된다. [[catchall]] 이중 괄호는 중첩 경로를 모두 잡는다.
Hono와 Pages Functions 통합
개별 파일 방식은 소규모에 적합하다. 라우터가 많아지면 Hono를 Pages Functions에 마운트하는 것이 낫다.
functions/
[[route]].ts ← 모든 경로를 Hono 앱에 위임
// functions/[[route]].ts
import { Hono } from 'hono'
import { handle } from 'hono/cloudflare-pages'
type Env = { DB: D1Database }
const app = new Hono<{ Bindings: Env }>()
app.get('/api/posts', async (c) => {
const { results } = await c.env.DB
.prepare('SELECT * FROM posts')
.all()
return c.json(results)
})
app.post('/api/posts', async (c) => {
const { title, content } = await c.req.json()
await c.env.DB
.prepare('INSERT INTO posts (title, content) VALUES (?, ?)')
.bind(title, content)
.run()
return c.json({ ok: true }, 201)
})
export const onRequest = handle(app)
handle(app) 이 Hono 앱을 Pages Functions의 onRequest 형태로 변환한다.
이 패턴의 장점 — 프론트엔드(dist/)와 API(functions/)가 같은 도메인(
my-site.pages.dev)에서 서비스되므로 CORS 설정이 필요 없다.
D1 바인딩 설정
Pages 프로젝트에서 D1을 쓰려면 Dashboard에서 바인딩을 추가해야 한다.
Dashboard → Workers & Pages → [프로젝트] → Settings → Functions
→ D1 database bindings → Add binding
→ Variable name: DB Database: [D1 데이터베이스 선택]
로컬 개발 시에는 wrangler.toml 또는 .dev.vars 로 바인딩을 시뮬레이션한다.
# wrangler.toml (Pages 프로젝트용)
name = "my-site"
pages_build_output_dir = "dist"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# 로컬 개발 서버 실행 (Functions + D1 로컬 시뮬레이션 포함)
npx wrangler pages dev ./dist
SPA 라우팅 처리
React Router, Vue Router 같은 클라이언트 사이드 라우터는 index.html 이 모든 경로에 응답해야 한다. Pages에서는 _redirects 파일로 처리한다.
# public/_redirects
/* /index.html 200
또는 _routes.json 으로 더 세밀하게 제어할 수 있다.
// public/_routes.json
{
"version": 1,
"include": ["/*"],
"exclude": ["/api/*", "/assets/*"]
}
exclude 에 포함된 경로는 Pages Functions로 넘어가지 않고 정적 파일을 직접 서빙한다.
환경 변수 & 미리보기 환경
# CLI로 환경 변수 설정
npx wrangler pages secret put API_KEY --project-name=my-site
# 미리보기(Preview) 환경에도 동일하게
npx wrangler pages secret put API_KEY --project-name=my-site --env=preview
// Functions 코드에서 접근
type Env = { API_KEY: string; DB: D1Database }
export const onRequestGet: PagesFunction<Env> = ({ env }) => {
console.log(env.API_KEY) // Wrangler가 주입
return Response.json({ ok: true })
}
브랜치별 미리보기 URL은 https://<branch-name>.<project-name>.pages.dev 형태다. PR을 열면 해당 브랜치의 빌드가 자동 배포되고 URL이 PR 코멘트에 올라온다.
핵심 정리
import KeyPoints from ’../../components/KeyPoints.astro’
<KeyPoints points={[ “Pages = 정적 CDN + Pages Functions(Workers) — 프론트엔드와 API를 같은 도메인에서 운영”, “functions/ 폴더 구조가 URL 경로에 1:1 매핑, [id].ts 파일이 동적 경로 파라미터”, “functions/[[route]].ts + Hono handle() 조합으로 대규모 API를 깔끔하게 관리”, “D1 바인딩은 Dashboard Settings → Functions 탭에서 설정, 로컬은 wrangler.toml”, “SPA 라우팅은 public/_redirects (/* /index.html 200) 한 줄로 해결” ]} />
import Callout from ’../../components/Callout.astro’