NestBook / Cloudflare + D1

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의 차이는 아래와 같다.

WorkersPages
주 용도API 서버, 백엔드 로직정적 사이트 + 가벼운 Functions
진입점src/index.ts exportfunctions/ 폴더 파일 구조
배포 방식wrangler deployGit push 또는 wrangler pages deploy
D1 바인딩✅ 완전 지원✅ 지원 (설정 위치 다름)

정적 사이트 배포

처음 연결 — GitHub/GitLab 연동

Cloudflare Dashboard → Workers & Pages → Create application → Pages → Connect to Git
→ 저장소 선택 → 빌드 명령·출력 디렉터리 설정

프레임워크별 기본값:

프레임워크빌드 명령출력 디렉터리
Astronpm run builddist
Next.jsnpm run build.next
Vite (React/Vue)npm run builddist
SvelteKitnpm run buildbuild
순수 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’

**다음 챕터 연결** — 텍스트 데이터는 D1으로 충분하지만 이미지·PDF·동영상 같은 파일은 별도 스토리지가 필요하다. 다음 챕터에서는 **Cloudflare R2** 로 오브젝트 스토리지를 구성하고, Presigned URL로 클라이언트에서 직접 업로드하는 패턴을 배운다.