常见错误排查
要点
- 排查错误遵循「从外到内」:终端报错 → 浏览器响应 → 代码逻辑
- 类型错误大多和 Env 泛型、await 遗漏、参数类型有关
- 运行时错误集中在环境变量未配置、路由匹配失败、Body 重复读取
- 部署错误通常是认证、包体积或 bindings 配置问题
wrangler tail是线上排查的核心工具- 系列第一篇结束,下一分组进入 Hono 核心能力深入
1. 排查思路:从外到内
遇到错误不要慌,按顺序检查三层:
第一层:终端输出。 wrangler dev 或 wrangler deploy 的报错信息通常最直白——编译错误、类型错误、配置错误都会在这里出现。先看终端,80% 的问题在这一步就能定位。
第二层:浏览器 / 响应。 如果终端没报错但行为不对,打开浏览器开发者工具的 Network 面板,看请求的 URL、状态码、响应体。404 是路由没匹配、500 是代码抛异常、CORS 错误是响应头缺失。
第三层:代码逻辑。 前两层都没发现明显问题,再逐行看代码。重点检查 await 是否遗漏、环境变量是否拼写正确、路由路径是否和请求一致。
养成这个顺序习惯,能避免「盯着代码看半天,发现终端早就报了错」的情况。
2. 类型错误
c.env 类型报错
错误信息:
// terminal
Property 'DB' does not exist on type '{}'.
原因:Hono 的 c.env 默认类型是空对象 {},TypeScript 不知道你的 Worker 绑定了哪些资源。
解决:定义 Env 类型并通过泛型注入:
// src/types/env.d.ts
export type Env = {
Bindings: {
DB: D1Database
API_SECRET: string
}
}// src/index.ts
import { Hono } from 'hono'
import type { Env } from './types/env'
const app = new Hono<Env>()
app.get('/api/users/:id', async (c) => {
const db = c.env.DB // ✅ 类型正确
const secret = c.env.API_SECRET // ✅ 类型正确
// ...
})如果 D1Database 等类型找不到,确认 tsconfig.json 的 types 里包含了 @cloudflare/workers-types。
c.req.json() 没 await
错误信息:
// terminal
Type 'Promise<any>' is not assignable to type '...'
原因:c.req.json() 返回的是 Promise,需要 await。handler 函数也要加 async。
// ❌ 错误
app.post('/api/users', (c) => {
const body = c.req.json() // body 是 Promise,不是数据
return c.json({ data: body })
})
// ✅ 正确
app.post('/api/users', async (c) => {
const body = await c.req.json()
return c.json({ data: body })
})同样的问题也出现在 c.req.text()、c.req.formData()、c.req.arrayBuffer() 上——它们都返回 Promise。
路由参数类型
c.req.param() 返回的始终是字符串。如果你需要数字类型,必须手动转换:
// src/index.ts
app.get('/api/users/:id', (c) => {
const id = c.req.param('id') // 类型是 string
// id === '42',不是 42
const numId = Number(id)
if (Number.isNaN(numId)) {
return c.json({ error: 'id must be a number' }, 400)
}
return c.json({ id: numId })
})不转换不会报编译错误,但后续用 === 比较或做数学运算时会出逻辑 bug。
3. 运行时错误
TypeError: c.env.XXX is undefined
错误信息:
// terminal
TypeError: Cannot read properties of undefined (reading 'prepare')
原因:在本地开发时 .dev.vars 里缺少对应的环境变量,或者 wrangler.jsonc 里的 bindings 没配置。
解决:
- 检查
.dev.vars是否包含所需的变量:
# .dev.vars
API_SECRET=dev-secret-key
DB=...- 对于 D1、KV、R2 等 bindings,确认
wrangler.jsonc里有对应配置。 - 重启
wrangler dev——修改.dev.vars后需要重启才能生效。
No matching route
访问接口返回 404,终端没有报错。常见原因:
- 路径拼写错误。 注册了
/api/users,请求的是/api/user(少了个 s)。 - 路由挂载前缀不对。 子 app 用
app.route('/api/users', users)挂载后,子 app 内的/对应完整路径/api/users,不是/api/users/(末尾斜杠可能不匹配)。 - HTTP 方法不对。 注册的是 POST,请求用的是 GET。
排查时先在终端看 wrangler 的请求日志,确认请求的完整路径和方法,再和代码里的路由定义逐一比对。
Body already read
错误信息:
// terminal
TypeError: Body already read
原因:Request 的 body 只能读取一次。如果你在中间件里读了 body,handler 里再读就会报错。
// ❌ 错误:body 被读了两次
app.use('/api/*', async (c, next) => {
const body = await c.req.json() // 第一次读取
await next()
})
app.post('/api/users', async (c) => {
const body = await c.req.json() // ❌ Body already read
})解决方案:用 c.req.parseBody() 或把解析结果存到 c.set() 里传递:
// ✅ 正确:中间件解析后存入 context
app.use('/api/*', async (c, next) => {
const body = await c.req.json()
c.set('parsedBody', body)
await next()
})
app.post('/api/users', async (c) => {
const body = c.get('parsedBody') // 从 context 取
})CORS 错误
浏览器控制台报 CORS 错误,但 curl 测试正常。原因是浏览器会先发 OPTIONS 预检请求,如果响应没有 CORS 头就会拦截。
解决:加 CORS 中间件,不要手写:
// src/index.ts
import { cors } from 'hono/cors'
app.use('/api/*', cors({
origin: ['http://localhost:3000', 'https://your-app.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
}))Hono 内置了 hono/cors 中间件,直接导入使用。origin 不要写 *,生产环境明确指定允许的域名。
4. 部署错误
wrangler deploy 认证失败
错误信息:
// terminal
A request to the Cloudflare API (/accounts/...) failed.
Authentication error
解决:
// terminal
npx wrangler login重新登录后再 deploy。如果公司网络有代理,可能需要设置 HTTPS_PROXY 环境变量。
包体积超限
错误信息:
// terminal
Error: The script exceeded the maximum total script size (allowed: 1048576 bytes, found: ...)
Cloudflare Workers 免费版限制脚本大小 1MB(gzip 后),付费版限制更大。排查方向:
- 检查是否把大型依赖(如
lodash完整版)打进了 bundle。用 tree-shaking 友好的导入方式:
// ❌ 引入整个库
import _ from 'lodash'
// ✅ 只引入需要的函数
import cloneDeep from 'lodash/cloneDeep'- 在
wrangler.jsonc里开启 minification:
// wrangler.jsonc
{
"minify": true
}- 用
npx wrangler deploy --dry-run查看打包后的文件大小,定位哪个依赖占比最大。
compatibility_date 问题
错误信息:
// terminal
A worker can specify at most one compatibility_date
或某些 API 行为和预期不一致。compatibility_date 决定了 Workers 运行时的行为版本。建议:
- 保持
wrangler.jsonc里只有一个compatibility_date - 使用近期日期,不要用太老的日期(会禁用新特性)
- 本地和生产保持一致
绑定服务未配置
部署后运行时 c.env.DB 是 undefined,但本地正常。原因:wrangler.jsonc 里的 bindings 配置只在本地或指定环境下生效,生产环境需要在 Cloudflare Dashboard 或 CI 里单独配置。
检查步骤:
- 打开 Cloudflare Dashboard → Workers → 你的 Worker → Settings → Variables
- 确认环境变量、Secrets、D1、KV 等 bindings 都已经配置
- 确认
database_id、id等 ID 和实际创建的资源一致
5. 本地开发错误
端口被占用
错误信息:
// terminal
Error: The port 8787 is already in use.
解决:
// terminal
# 方案一:指定其他端口
npx wrangler dev --port 3000
# 方案二:找到并关闭占用端口的进程
lsof -i :8787
kill <PID>.dev.vars 没加载
本地运行时 c.env.API_SECRET 是 undefined,但 .dev.vars 里明明写了。排查:
- 文件位置不对。
.dev.vars必须在项目根目录(和wrangler.jsonc同级),不是src/里。 - 格式错误。
.dev.vars是纯文本键值对,不是 JSON:
# ✅ 正确格式
API_SECRET=my-secret-key
JWT_SECRET=another-secret
# ❌ 错误格式
{ "API_SECRET": "my-secret-key" }- 没重启 wrangler。 修改
.dev.vars后必须重启wrangler dev。 - 文件名拼写。 是
.dev.vars不是.dev.var,注意有个s。
热更新不生效
修改代码后本地服务没有自动刷新。wrangler 默认支持热更新,如果不生效:
- 确认是在
wrangler dev模式下运行,不是wrangler dev --local(某些旧版本本地模式热更新有 bug) - 确认文件确实保存了(编辑器有时设置了自动保存但延迟较长)
- 修改
wrangler.jsonc不触发热更新,需要重启
6. 性能相关问题
CPU 时间超限
错误信息:
// terminal
Exceeded CPU limit
Cloudflare Workers 免费版每次请求最多 10ms CPU 时间,付费版 50ms。常见触发场景:
- 在 Worker 里做大量 JSON 解析或字符串处理
- 循环调用外部 API
- 处理大文件(几 MB 级别)
解决方案:
- 把重计算逻辑移到 Durable Objects 或专门的 Worker
- 减少单次请求的处理量,改用分页
- 使用 KV 或 D1 缓存计算结果
内存超限
错误信息:
// terminal
Exceeded memory limit
Workers 免费版内存限制 128MB。通常不会触发,除非:
- 一次性把大文件全部读进内存(应该用流式处理)
- 在内存里缓存了大量数据(应该用 KV)
// ❌ 一次性读取大文件
const buffer = await request.arrayBuffer()
// ✅ 流式处理
const reader = request.body.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) break
// 逐块处理
}请求超时
错误信息:浏览器或客户端显示请求超时,但 Workers 日志显示请求成功了。
原因:Workers 免费版有 30 秒的 wall clock 超时。如果请求涉及外部 API 调用,外部 API 响应慢就会触发。
解决:
- 给外部 API 调用加超时控制:
// src/index.ts
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 5000)
try {
const res = await fetch('https://api.example.com/data', {
signal: controller.signal,
})
return c.json(await res.json())
} catch (e) {
return c.json({ error: 'Upstream timeout' }, 504)
} finally {
clearTimeout(timeout)
}- 升级到付费版可以把超时调到更长时间
- 对于耗时操作,改用异步任务模式(客户端轮询或 WebSocket 通知)
7. 排查工具箱
wrangler tail:线上实时日志
// terminal
npx wrangler tail执行后,所有到达线上 Worker 的请求都会实时打印在终端,包括 console.log() 的输出和异常堆栈。这是定位线上问题最直接的方式。
支持过滤:
// terminal
# 只看某个路径的请求
npx wrangler tail --format=pretty --path=/api/users
# 按状态码过滤
npx wrangler tail --status=500Cloudflare Dashboard Logs
打开 Cloudflare Dashboard → Workers → 你的 Worker → Logs,可以看到完整的请求日志和错误信息。比 wrangler tail 更适合回溯历史请求。
浏览器 Network 面板
前端调 API 出问题时,Network 面板是第一现场。重点看:
- Request URL:确认请求路径是否正确
- Status Code:4xx 是客户端问题(路径错、参数错),5xx 是服务端问题
- Response Body:后端返回的错误信息通常在这里
- Headers:检查 CORS 头、Content-Type 等
console.log() 大法
不要小看 console.log()。在 Workers 环境里,console.log() 的输出会出现在 wrangler tail 和 Dashboard Logs 里。排查时加几行日志,比断点调试快得多:
// src/index.ts
app.post('/api/users', async (c) => {
console.log('Received request:', c.req.method, c.req.url)
const body = await c.req.json()
console.log('Request body:', JSON.stringify(body))
// ... 业务逻辑
console.log('Response:', result)
return c.json(result)
})排查完记得删掉,或者用条件判断只在开发环境打印。
延伸阅读
总结
Hono + Cloudflare Workers 开发中遇到的错误基本可以归为三类:编译阶段(类型定义缺失、await 遗漏)、运行阶段(环境变量未配置、路由不匹配、Body 重复读取)、部署阶段(认证失败、包体积超限、bindings 未配置)。排查时遵循「终端 → 浏览器 → 代码」的顺序,配合 wrangler tail 和 Network 面板,绝大多数问题都能快速定位。
到这里,「02-Hono 快速入门」分组就结束了。回顾一下这个分组的学习路线:
- 了解 Hono 是什么、为什么选它
- 初始化第一个项目,跑通本地开发和部署
- 设计项目目录结构
- 编写基础路由、实现 CRUD 接口
- 处理请求参数和响应格式
- 配置环境变量和 Secrets
- 本地开发与热更新
- 常见错误排查
从「Hello Hono!」到能独立开发、部署、排查问题,这条路线覆盖了入门阶段需要掌握的全部基础能力。
下一个分组「Hono 核心能力深入」会在这个基础上往深处走:中间件机制、请求校验、错误处理模式、与 D1/KV 的深度集成、认证鉴权实战。那些才是构建真实应用的核心拼图。
先把手头的项目跑起来,遇到报错翻回这篇查。准备好了就进入下一分组。