Hono 的一大优势是原生支持 Cloudflare Workers、Deno、Bun 等边缘运行时。本文以 Cloudflare Workers 为例,详细介绍部署流程和性能优化策略。
wrangler.toml 配置
Cloudflare Workers 使用 wrangler.toml 管理项目配置:
name = "my-hono-app"
main = "src/index.ts"
compatibility_date = "2025-01-01"
# 环境变量配置
[vars]
JWT_SECRET = "your-secret-key"
DATABASE_URL = "https://db.example.com"
APP_ENV = "production"
# R2 存储桶绑定
[[r2_buckets]]
binding = "ASSETS_BUCKET"
bucket_name = "my-app-assets"
# KV 命名空间绑定
[[kv_namespaces]]
binding = "CACHE_KV"
id = "your-kv-namespace-id"
# D1 数据库绑定
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "your-database-id"
环境变量管理
在代码中通过 c.env 访问环境变量:
import { Hono } from 'hono'
type Bindings = {
JWT_SECRET: string
APP_ENV: string
ASSETS_BUCKET: R2Bucket
CACHE_KV: KVNamespace
DB: D1Database
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/config', (c) => {
return c.json({
env: c.env.APP_ENV,
// 注意:切忌将敏感信息返回给客户端
})
})
本地开发时可以在 .dev.vars 文件中设置环境变量:
JWT_SECRET=local-dev-secret
DATABASE_URL=http://localhost:3000
部署到 Cloudflare Workers
登录 Cloudflare 账号并部署:
# 登录 Cloudflare
bun wrangler login
# 部署到生产环境
bun wrangler deploy
# 部署到预览环境
bun wrangler deploy --env preview
部署完成后,你会获得一个 *.workers.dev 域名。你也可以在 Cloudflare 控制台中绑定自定义域名。
R2 文件存储
R2 是 Cloudflare 的对象存储服务,兼容 S3 API,且没有出口流量费:
app.post('/upload', async (c) => {
const bucket = c.env.ASSETS_BUCKET
const formData = await c.req.formData()
const file = formData.get('file') as File | null
if (!file) {
return c.json({ error: '请选择文件' }, 400)
}
// 生成唯一文件名
const key = `${Date.now()}-${file.name}`
const arrayBuffer = await file.arrayBuffer()
await bucket.put(key, arrayBuffer, {
httpMetadata: { contentType: file.type },
customMetadata: {
uploadedBy: 'hono-app',
},
})
return c.json({
url: `https://assets.example.com/${key}`,
key,
size: file.size,
})
})
app.get('/files/:key', async (c) => {
const bucket = c.env.ASSETS_BUCKET
const key = c.req.param('key')
const object = await bucket.get(key)
if (!object) {
return c.json({ error: '文件未找到' }, 404)
}
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
'Cache-Control': 'public, max-age=31536000',
},
})
})
缓存策略
使用 Cache API
Cloudflare Workers 提供了 Cache API,可以将响应缓存到边缘节点:
app.get('/api/posts', async (c) => {
const cache = caches.default
const cacheKey = new Request(c.req.url)
const cachedResponse = await cache.match(cacheKey)
if (cachedResponse) {
return cachedResponse
}
// 模拟数据库查询
const posts = [
{ id: 1, title: '文章一' },
{ id: 2, title: '文章二' },
]
const response = c.json({ posts })
// 设置缓存,有效期 5 分钟
response.headers.set('Cache-Control', 'public, max-age=300')
c.executionCtx.waitUntil(cache.put(cacheKey, response.clone()))
return response
})
使用 KV 做缓存层
KV 适合存储频繁读取但不常修改的数据:
app.get('/api/popular', async (c) => {
const cacheKey = 'popular_posts'
const cached = await c.env.CACHE_KV.get(cacheKey)
if (cached) {
return c.json(JSON.parse(cached))
}
const data = { posts: ['热门文章1', '热门文章2'] }
// 缓存 1 小时
await c.env.CACHE_KV.put(cacheKey, JSON.stringify(data), {
expirationTtl: 3600,
})
return c.json(data)
})
条件请求
对于频繁变动的资源,使用 ETag 或 Last-Modified 减少传输:
app.get('/api/data', async (c) => {
const data = { version: 2, content: '最新数据' }
const etag = `"${crypto.randomUUID()}"`
// 检查客户端缓存
const ifNoneMatch = c.req.header('If-None-Match')
if (ifNoneMatch === etag) {
return new Response(null, { status: 304 })
}
const response = c.json(data)
response.headers.set('ETag', etag)
return response
})
日志与监控
结构化日志
使用 console.log 配合 JSON 格式输出,方便日志系统收集:
app.use('*', async (c, next) => {
const start = Date.now()
await next()
const duration = Date.now() - start
console.log(JSON.stringify({
level: 'info',
method: c.req.method,
path: c.req.path,
status: c.res.status,
duration,
timestamp: new Date().toISOString(),
}))
})
请求 ID 追踪
为每个请求分配唯一 ID,便于追踪问题:
import type { MiddlewareHandler } from 'hono'
const requestId: MiddlewareHandler = async (c, next) => {
const id = crypto.randomUUID()
c.set('requestId', id)
c.res.headers.set('X-Request-Id', id)
await next()
}
app.use('*', requestId)
错误告警
生产环境应当记录异常到外部监控服务:
app.onError(async (err, c) => {
const requestId = c.get('requestId') || 'unknown'
// 发送到日志服务(示例使用 fetch)
await fetch('https://logs.example.com/error', {
method: 'POST',
body: JSON.stringify({
requestId,
error: err.message,
stack: err.stack,
path: c.req.path,
}),
})
return c.json({
error: '服务器内部错误',
requestId,
}, 500)
})
性能优化建议
减少包体积
Hono 本身就很小,但注意不要引入不必要的依赖。用 bun run build 检查构建产物大小。
使用响应式缓存
对 GET 请求优先使用 Cache API 或 KV 缓存,减少回源请求。
预热
对于已知的热门数据,在 Workers 启动时通过 scheduled 事件预填充缓存:
export default {
fetch: app.fetch,
async scheduled(event: ScheduledEvent, env: any, ctx: ExecutionContext) {
// 每小时刷新缓存
const data = await fetchPopularData()
await env.CACHE_KV.put('popular', JSON.stringify(data))
},
}
压缩响应
Cloudflare 会自动对响应进行 Brotli / Gzip 压缩,确保响应内容不要过于冗余。
小结
本文介绍了 Hono 应用在 Cloudflare Workers 上的完整部署流程,包括 wrangler 配置、环境变量管理、R2 文件存储、缓存策略和日志监控。借助 Cloudflare 的边缘网络,你的 Hono 应用可以获得全球范围内的低延迟访问。
至此,Hono 教程系列的四篇文章全部完成。你应该已经能够独立使用 Hono 构建、部署和优化一个生产级别的 Web 应用了。