Skip to content

Cloudflare Pages

Cloudflare Pages는 풀스택 웹 애플리케이션을 위한 엣지 플랫폼이다. 정적 파일과 Cloudflare Workers가 제공하는 동적 콘텐츠를 서빙한다.

Hono는 Cloudflare Pages를 완벽하게 지원한다. 개발자에게 즐거운 경험을 제공한다. Vite의 개발 서버는 빠르고, Wrangler로 배포하는 과정도 매우 간단하다.

1. 설정

Cloudflare Pages를 위한 스타터 프로젝트가 준비되어 있다. "create-hono" 커맨드를 사용해 프로젝트를 시작한다. 이 예제에서는 cloudflare-pages 템플릿을 선택한다.

sh
npm create hono@latest my-app
sh
yarn create hono my-app
sh
pnpm create hono my-app
sh
bun create hono@latest my-app
sh
deno init --npm hono my-app

my-app 디렉터리로 이동한 후 의존성을 설치한다.

sh
cd my-app
npm i
sh
cd my-app
yarn
sh
cd my-app
pnpm i
sh
cd my-app
bun i

아래는 기본 디렉터리 구조다.

text
./
├── package.json
├── public
│   └── static // 정적 파일을 저장한다.
│       └── style.css // `/static/style.css`로 참조할 수 있다.
├── src
│   ├── index.tsx // 서버 사이드의 진입점이다.
│   └── renderer.tsx
├── tsconfig.json
└── vite.config.ts

2. Hello World

src/index.tsx 파일을 다음과 같이 수정한다:

tsx
import { Hono } from 'hono'
import { renderer } from './renderer'

const app = new Hono()

app.get('*', renderer)

app.get('/', (c) => {
  return c.render(<h1>Hello, Cloudflare Pages!</h1>)
})

export default app

3. 실행

로컬에서 개발 서버를 실행한다. 그런 다음 웹 브라우저에서 http://localhost:5173에 접속한다.

sh
npm run dev
sh
yarn dev
sh
pnpm dev
sh
bun run dev

4. 배포

Cloudflare 계정이 있다면 Cloudflare에 배포할 수 있다. package.json에서 $npm_execpath를 원하는 패키지 관리자로 변경해야 한다.

sh
npm run deploy
sh
yarn deploy
sh
pnpm run deploy
sh
bun run deploy

Cloudflare 대시보드와 GitHub를 통해 배포하기

  1. Cloudflare 대시보드에 로그인한 후 계정을 선택한다.
  2. Account Home에서 Workers & Pages > Create application > Pages > Connect to Git을 선택한다.
  3. GitHub 계정을 인증하고 리포지토리를 선택한다. Set up builds and deployments에서 다음 정보를 입력한다:
설정 옵션
Production branchmain
Build commandnpm run build
Build directorydist

바인딩

Cloudflare에서 제공하는 바인딩 기능을 활용할 수 있다. Variables, KV, D1 등 다양한 바인딩이 존재한다. 이번 섹션에서는 Variables와 KV를 사용해 본다.

wrangler.toml 파일 생성

먼저, 로컬 바인딩을 위해 wrangler.toml 파일을 생성한다:

sh
touch wrangler.toml

wrangler.toml 파일을 편집한다. MY_NAME이라는 이름의 변수를 지정한다.

toml
[vars]
MY_NAME = "Hono"

KV 생성

다음으로 KV를 생성한다. 아래 wrangler 커맨드를 실행한다:

sh
wrangler kv namespace create MY_KV --preview

출력 결과에서 preview_id를 기록한다:

{ binding = "MY_KV", preview_id = "abcdef" }

바인딩 이름 MY_KV와 함께 preview_id를 지정한다:

toml
[[kv_namespaces]]
binding = "MY_KV"
id = "abcdef"

vite.config.ts 수정

vite.config.ts를 다음과 같이 수정한다:

ts
import devServer from '@hono/vite-dev-server'
import adapter from '@hono/vite-dev-server/cloudflare'
import build from '@hono/vite-cloudflare-pages'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    devServer({
      entry: 'src/index.tsx',
      adapter, // Cloudflare Adapter
    }),
    build(),
  ],
})

애플리케이션에서 바인딩 사용하기

애플리케이션에서 변수와 KV를 사용해 보자. 타입을 설정한다.

ts
type Bindings = {
  MY_NAME: string
  MY_KV: KVNamespace
}

const app = new Hono<{ Bindings: Bindings }>()

이제 이렇게 사용한다:

tsx
app.get('/', async (c) => {
  await c.env.MY_KV.put('name', c.env.MY_NAME)
  const name = await c.env.MY_KV.get('name')
  return c.render(<h1>Hello! {name}</h1>)
})

프로덕션 환경

Cloudflare Pages를 사용할 때, 로컬 개발 환경에서는 wrangler.toml을 사용한다. 하지만 프로덕션 환경에서는 대시보드에서 Bindings를 설정해야 한다.

클라이언트 사이드

Vite의 기능을 활용해 클라이언트 사이드 스크립트를 작성하고 애플리케이션에 임포트할 수 있다. /src/client.ts가 클라이언트의 진입점이라면, 스크립트 태그에 간단히 작성하면 된다. 또한 import.meta.env.PROD를 사용해 개발 서버에서 실행 중인지 빌드 단계인지 확인할 수 있다.

tsx
app.get('/', (c) => {
  return c.html(
    <html>
      <head>
        {import.meta.env.PROD ? (
          <script type='module' src='/static/client.js'></script>
        ) : (
          <script type='module' src='/src/client.ts'></script>
        )}
      </head>
      <body>
        <h1>Hello</h1>
      </body>
    </html>
  )
})

스크립트를 올바르게 빌드하기 위해 아래와 같은 vite.config.ts 설정 파일 예제를 사용할 수 있다.

ts
import pages from '@hono/vite-cloudflare-pages'
import devServer from '@hono/vite-dev-server'
import { defineConfig } from 'vite'

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      build: {
        rollupOptions: {
          input: './src/client.ts',
          output: {
            entryFileNames: 'static/client.js',
          },
        },
      },
    }
  } else {
    return {
      plugins: [
        pages(),
        devServer({
          entry: 'src/index.tsx',
        }),
      ],
    }
  }
})

다음 커맨드를 실행해 서버와 클라이언트 스크립트를 빌드할 수 있다.

sh
vite build --mode client && vite build

Cloudflare Pages 미들웨어

Cloudflare Pages는 Hono의 미들웨어와 다른 자체 미들웨어 시스템을 사용한다. _middleware.ts 파일에서 onRequest를 내보내면 이를 활성화할 수 있다.

ts
// functions/_middleware.ts
export async function onRequest(pagesContext) {
  console.log(`현재 접속 중인 URL: ${pagesContext.request.url}`)
  return await pagesContext.next()
}

handleMiddleware를 사용하면 Hono의 미들웨어를 Cloudflare Pages 미들웨어로 활용할 수 있다.

ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'

export const onRequest = handleMiddleware(async (c, next) => {
  console.log(`현재 접속 중인 URL: ${c.req.url}`)
  await next()
})

Hono의 내장 미들웨어와 서드파티 미들웨어도 사용할 수 있다. 예를 들어, 기본 인증을 추가하려면 Hono의 기본 인증 미들웨어를 활용한다.

ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'
import { basicAuth } from 'hono/basic-auth'

export const onRequest = handleMiddleware(
  basicAuth({
    username: 'hono',
    password: 'acoolproject',
  })
)

여러 미들웨어를 적용하려면 다음과 같이 작성한다:

ts
import { handleMiddleware } from 'hono/cloudflare-pages'

// ...

export const onRequest = [
  handleMiddleware(middleware1),
  handleMiddleware(middleware2),
  handleMiddleware(middleware3),
]

EventContext 접근하기

handleMiddleware에서 c.env를 통해 EventContext 객체에 접근할 수 있다.

ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'

export const onRequest = [
  handleMiddleware(async (c, next) => {
    c.env.eventContext.data.user = 'Joe'
    await next()
  }),
]

그런 다음, 핸들러에서 c.env.eventContext를 통해 데이터 값을 접근할 수 있다:

ts
// functions/api/[[route]].ts
import type { EventContext } from 'hono/cloudflare-pages'
import { handle } from 'hono/cloudflare-pages'

// ...

type Env = {
  Bindings: {
    eventContext: EventContext
  }
}

const app = new Hono<Env>()

app.get('/hello', (c) => {
  return c.json({
    message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe'
  })
})

export const onRequest = handle(app)

Released under the MIT License.