Cloudflare Pages

Cloudflare Pages 是一个用于全栈 Web 应用的边缘平台。它既可以提供静态文件,也可以通过 Cloudflare Workers 提供动态内容。

Hono 完全支持 Cloudflare Pages,带来非常优秀的开发体验。Vite 的开发服务器非常快速,而通过 Wrangler 部署也极其便捷。

1. 安装

Cloudflare Pages 提供了 Starter 模板。使用 create-hono 命令启动项目,本例选择 cloudflare-pages 模板。

npm

  1. npm create hono@latest my-app

yarn

  1. yarn create hono my-app

pnpm

  1. pnpm create hono@latest my-app

bun

  1. bun create hono@latest my-app

deno

  1. deno init --npm hono my-app

进入 my-app 目录并安装依赖。

npm

  1. cd my-app
  2. npm i

yarn

  1. cd my-app
  2. yarn

pnpm

  1. cd my-app
  2. pnpm i

bun

  1. cd my-app
  2. bun i

下面是一个基本目录结构:

  1. ./
  2. ├── package.json
  3. ├── public
  4. └── static // 放置静态文件
  5. └── style.css // 通过 `/static/style.css` 引用
  6. ├── src
  7. ├── index.tsx // 服务端入口文件
  8. └── renderer.tsx
  9. ├── tsconfig.json
  10. └── vite.config.ts

2. Hello World

编辑 src/index.tsx

  1. import { Hono } from 'hono'
  2. import { renderer } from './renderer'
  3. const app = new Hono()
  4. app.get('*', renderer)
  5. app.get('/', (c) => {
  6. return c.render(<h1>Hello, Cloudflare Pages!</h1>)
  7. })
  8. export default app

3. 运行

本地运行开发服务器,然后访问 http://localhost:5173

npm

  1. npm run dev

yarn

  1. yarn dev

pnpm

  1. pnpm dev

bun

  1. bun run dev

4. 部署

如果有 Cloudflare 账号,可以部署到 Cloudflare。记得在 package.json 中将 $npm_execpath 改为你的包管理器。

npm

  1. npm run deploy

yarn

  1. yarn deploy

pnpm

  1. pnpm run deploy

bun

  1. bun run deploy

通过 Cloudflare 控制台 + GitHub 部署

  1. 登录 Cloudflare dashboard,选择账号。
  2. 在 Account Home 中,进入 Workers & Pages > Create application > Pages > Connect to Git。
  3. 授权 GitHub 账号并选择仓库,在构建配置中填写:
配置项
Production branch main
Build command npm run build
Build directory dist

Bindings

你可以使用 Cloudflare Bindings,如 Variables、KV、D1 等。以下例子展示了如何使用 Variables 和 KV。

创建 wrangler.toml

首先,为本地 Bindings 创建 wrangler.toml

  1. touch wrangler.toml

编辑 wrangler.toml,添加变量 MY_NAME

  1. [vars]
  2. MY_NAME = "Hono"

创建 KV

创建 KV 命名空间:

  1. wrangler kv namespace create MY_KV --preview

记录输出的 preview_id,并在配置中指定:

  1. [[kv_namespaces]]
  2. binding = "MY_KV"
  3. id = "abcdef"

编辑 vite.config.ts

配置 Vite:

  1. import devServer from '@hono/vite-dev-server'
  2. import adapter from '@hono/vite-dev-server/cloudflare'
  3. import build from '@hono/vite-cloudflare-pages'
  4. import { defineConfig } from 'vite'
  5. export default defineConfig({
  6. plugins: [
  7. devServer({
  8. entry: 'src/index.tsx',
  9. adapter, // Cloudflare Adapter
  10. }),
  11. build(),
  12. ],
  13. })

在应用中使用 Bindings

  1. type Bindings = {
  2. MY_NAME: string
  3. MY_KV: KVNamespace
  4. }
  5. const app = new Hono<{ Bindings: Bindings }>()

示例代码:

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

生产环境配置

在 Cloudflare Pages 上,wrangler.toml 只用于本地开发,生产环境需在控制台配置 Bindings。


Client-side

你可以编写客户端脚本并通过 Vite 导入。例如 /src/client.ts 是客户端入口:

  1. app.get('/', (c) => {
  2. return c.html(
  3. <html>
  4. <head>
  5. {import.meta.env.PROD ? (
  6. <script type='module' src='/static/client.js'></script>
  7. ) : (
  8. <script type='module' src='/src/client.ts'></script>
  9. )}
  10. </head>
  11. <body>
  12. <h1>Hello</h1>
  13. </body>
  14. </html>
  15. )
  16. })

使用如下 vite.config.ts

  1. import pages from '@hono/vite-cloudflare-pages'
  2. import devServer from '@hono/vite-dev-server'
  3. import { defineConfig } from 'vite'
  4. export default defineConfig(({ mode }) => {
  5. if (mode === 'client') {
  6. return {
  7. build: {
  8. rollupOptions: {
  9. input: './src/client.ts',
  10. output: {
  11. entryFileNames: 'static/client.js',
  12. },
  13. },
  14. },
  15. }
  16. } else {
  17. return {
  18. plugins: [
  19. pages(),
  20. devServer({
  21. entry: 'src/index.tsx',
  22. }),
  23. ],
  24. }
  25. }
  26. })

构建客户端与服务端:

  1. vite build --mode client && vite build

Cloudflare Pages Middleware

Cloudflare Pages 使用独立的 middleware 系统,与 Hono 的 middleware 不同。你可以在 _middleware.ts 中导出 onRequest

  1. // functions/_middleware.ts
  2. export async function onRequest(pagesContext) {
  3. console.log(`You are accessing ${pagesContext.request.url}`)
  4. return await pagesContext.next()
  5. }

使用 handleMiddleware 可以让 Hono 的 middleware 在 Pages 中生效:

  1. import { handleMiddleware } from 'hono/cloudflare-pages'
  2. export const onRequest = handleMiddleware(async (c, next) => {
  3. console.log(`You are accessing ${c.req.url}`)
  4. await next()
  5. })

使用多重 Middleware

  1. export const onRequest = [
  2. handleMiddleware(middleware1),
  3. handleMiddleware(middleware2),
  4. handleMiddleware(middleware3),
  5. ]

访问 EventContext

可以通过 c.env 获取 EventContext 对象:

  1. // functions/_middleware.ts
  2. export const onRequest = [
  3. handleMiddleware(async (c, next) => {
  4. c.env.eventContext.data.user = 'Joe'
  5. await next()
  6. }),
  7. ]

在 API handler 中:

  1. // functions/api/[[route]].ts
  2. import type { EventContext } from 'hono/cloudflare-pages'
  3. import { handle } from 'hono/cloudflare-pages'
  4. type Env = {
  5. Bindings: {
  6. eventContext: EventContext
  7. }
  8. }
  9. const app = new Hono<Env>().basePath("/api")
  10. app.get('/hello', (c) => {
  11. return c.json({
  12. message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe'
  13. })
  14. })
  15. export const onRequest = handle(app)