常见错误排查

要点

  • 排查错误遵循「从外到内」:终端报错 → 浏览器响应 → 代码逻辑
  • 类型错误大多和 Env 泛型、await 遗漏、参数类型有关
  • 运行时错误集中在环境变量未配置、路由匹配失败、Body 重复读取
  • 部署错误通常是认证、包体积或 bindings 配置问题
  • wrangler tail 是线上排查的核心工具
  • 系列第一篇结束,下一分组进入 Hono 核心能力深入

1. 排查思路:从外到内

遇到错误不要慌,按顺序检查三层:

第一层:终端输出。 wrangler devwrangler 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.jsontypes 里包含了 @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 没配置。

解决:

  1. 检查 .dev.vars 是否包含所需的变量:
# .dev.vars
API_SECRET=dev-secret-key
DB=...
  1. 对于 D1、KV、R2 等 bindings,确认 wrangler.jsonc 里有对应配置。
  2. 重启 wrangler dev——修改 .dev.vars 后需要重启才能生效。

No matching route

访问接口返回 404,终端没有报错。常见原因:

  1. 路径拼写错误。 注册了 /api/users,请求的是 /api/user(少了个 s)。
  2. 路由挂载前缀不对。 子 app 用 app.route('/api/users', users) 挂载后,子 app 内的 / 对应完整路径 /api/users,不是 /api/users/(末尾斜杠可能不匹配)。
  3. 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 后),付费版限制更大。排查方向:

  1. 检查是否把大型依赖(如 lodash 完整版)打进了 bundle。用 tree-shaking 友好的导入方式:
// ❌ 引入整个库
import _ from 'lodash'
 
// ✅ 只引入需要的函数
import cloneDeep from 'lodash/cloneDeep'
  1. wrangler.jsonc 里开启 minification:
// wrangler.jsonc
{
  "minify": true
}
  1. 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.DBundefined,但本地正常。原因:wrangler.jsonc 里的 bindings 配置只在本地或指定环境下生效,生产环境需要在 Cloudflare Dashboard 或 CI 里单独配置。

检查步骤:

  1. 打开 Cloudflare Dashboard → Workers → 你的 Worker → Settings → Variables
  2. 确认环境变量、Secrets、D1、KV 等 bindings 都已经配置
  3. 确认 database_idid 等 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_SECRETundefined,但 .dev.vars 里明明写了。排查:

  1. 文件位置不对。 .dev.vars 必须在项目根目录(和 wrangler.jsonc 同级),不是 src/ 里。
  2. 格式错误。 .dev.vars 是纯文本键值对,不是 JSON:
# ✅ 正确格式
API_SECRET=my-secret-key
JWT_SECRET=another-secret
 
# ❌ 错误格式
{ "API_SECRET": "my-secret-key" }
  1. 没重启 wrangler。 修改 .dev.vars 后必须重启 wrangler dev
  2. 文件名拼写。.dev.vars 不是 .dev.var,注意有个 s

热更新不生效

修改代码后本地服务没有自动刷新。wrangler 默认支持热更新,如果不生效:

  1. 确认是在 wrangler dev 模式下运行,不是 wrangler dev --local(某些旧版本本地模式热更新有 bug)
  2. 确认文件确实保存了(编辑器有时设置了自动保存但延迟较长)
  3. 修改 wrangler.jsonc 不触发热更新,需要重启

6. 性能相关问题

CPU 时间超限

错误信息:

// terminal
Exceeded CPU limit

Cloudflare Workers 免费版每次请求最多 10ms CPU 时间,付费版 50ms。常见触发场景:

  • 在 Worker 里做大量 JSON 解析或字符串处理
  • 循环调用外部 API
  • 处理大文件(几 MB 级别)

解决方案:

  1. 把重计算逻辑移到 Durable Objects 或专门的 Worker
  2. 减少单次请求的处理量,改用分页
  3. 使用 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 响应慢就会触发。

解决:

  1. 给外部 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)
}
  1. 升级到付费版可以把超时调到更长时间
  2. 对于耗时操作,改用异步任务模式(客户端轮询或 WebSocket 通知)

7. 排查工具箱

wrangler tail:线上实时日志

// terminal
npx wrangler tail

执行后,所有到达线上 Worker 的请求都会实时打印在终端,包括 console.log() 的输出和异常堆栈。这是定位线上问题最直接的方式。

支持过滤:

// terminal
# 只看某个路径的请求
npx wrangler tail --format=pretty --path=/api/users
 
# 按状态码过滤
npx wrangler tail --status=500

Cloudflare 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 快速入门」分组就结束了。回顾一下这个分组的学习路线:

  1. 了解 Hono 是什么、为什么选它
  2. 初始化第一个项目,跑通本地开发和部署
  3. 设计项目目录结构
  4. 编写基础路由、实现 CRUD 接口
  5. 处理请求参数和响应格式
  6. 配置环境变量和 Secrets
  7. 本地开发与热更新
  8. 常见错误排查

从「Hello Hono!」到能独立开发、部署、排查问题,这条路线覆盖了入门阶段需要掌握的全部基础能力。

下一个分组「Hono 核心能力深入」会在这个基础上往深处走:中间件机制、请求校验、错误处理模式、与 D1/KV 的深度集成、认证鉴权实战。那些才是构建真实应用的核心拼图。

先把手头的项目跑起来,遇到报错翻回这篇查。准备好了就进入下一分组。