前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版

Next.js 16 接入 Sentry 与飞书告警实战,sentry-hook-signature 验签、堆栈渲染与踩坑排查

首页2026-09-14 10:12:36Front-End
SentryNext.js飞书错误监控可观测性告警

文章首发于: https://feinterview.poetries.top/blog/nextjs-sentry-feishu-alert

服务端报错只落在容器的 stdout 里,而容器日志是轮转的(--log-opt max-size),等有人在群里说「你那个页面白屏了」,我再去 docker logs,那段往往已经被冲掉了。浏览器端更彻底,压根看不见。更尴尬的是代码里那个组件级 ErrorBoundary,componentDidCatch 里只有一句 console.error,而生产构建开着 compiler.removeConsole,所以线上那个分支什么都没做,组件静默消失,没有任何人知道。

这周把这件事补上了:Sentry 收错误,飞书群收告警,卡片里直接带调用栈。整条链路从装包到上线跑通,中间踩了七八个坑,有几个是真的把我卡住了一阵,比如「本地怎么试都收不到上报」和「验签永远失败」。这篇就按我实际走的顺序记下来。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • @sentry/nextjs 10.x 在 Next.js 16 里三端(client / server / edge)怎么接线,各自的入口文件放哪
  • 为什么本地 yarn dev 收不到任何上报,以及这是我刻意做的
  • beforeSend 脱敏层要防什么,以及怎么不发一条数据给 Sentry 就验证它生效了
  • Sentry 的 exception.values[].stacktrace.frames 为什么顺序是反的,渲染进卡片前要怎么处理
  • webhook 路由的完整实现:验签、payload 解析、堆栈渲染、推卡片,四段代码都给全
  • sentry-hook-signature 验签失败,为什么多半是因为你用了 JSON.stringify(req.body)
  • 飞书自建应用从建应用到发版怎么配:机器人能力、权限批量导入、事件订阅,以及配了不发版不生效
  • 飞书推交互卡片的两个代码坑:content 必须是字符串、tenant_access_token 必须缓存
  • 集成建好了却在告警规则里选不到,是漏了哪个开关
  • next start 在 output: 'standalone' 下直接不工作,本地怎么验生产行为
  • 上线后那两次「以为出事了其实没有」的误判,怎么用对照实验证伪

# 一、整体架构

先把链路画清楚,后面每个坑落在哪一段就有地方挂了:

浏览器                      我们的 Next.js 容器                   外部服务
┌──────────┐               ┌──────────────────────────────┐    ┌─────────────┐
│ 页面 JS  │               │ instrumentation.ts           │    │   Sentry    │
│          │──── 错误 ────▶│   register() 按 runtime 加载  │───▶│ (poetry-org)│
│ instrume-│   (直连或经   │   onRequestError 钩子         │    │             │
│ ntation- │   /monitoring │                              │    └──────┬──────┘
│ client   │    隧道)      │ src/libs/sentry/             │           │
└──────────┘               │   beforeSend 统一脱敏         │           │ 告警规则触发
                           │                              │           │ event_alert
                           │ /api/alerts/sentry-webhook   │◀──────────┘ (POST + 签名)
                           │   HMAC 验签 → 解析堆栈        │
                           │        │                     │    ┌─────────────┐
                           │        ▼                     │    │  飞书自建应用 │
                           │ server/notify/feishu.ts      │───▶│  告警群       │
                           │   token 缓存 + 节流 + 卡片    │    └─────────────┘
                           │ server/notify/alert.ts       │
                           │   邮件 + 飞书 并行扇出         │    ┌─────────────┐
                           │                              │───▶│  阿里企业邮   │
                           └──────────────────────────────┘    └─────────────┘
@前端进阶之旅: 代码已经复制到剪贴板

三件事值得先说清楚:

告警中转放在了自己的站点里,不是独立服务。少一套部署、少一份凭据,跟着现有 Docker 一起发版。代价是站点整个挂掉时这条通道也哑,但那个场景 Sentry 自带的邮件会兜底,而且站全挂通常比单个 issue 更早被别的方式发现。真正需要覆盖的是「站点活着但某个功能在报错」,那正是这条链路的主场。

邮件和飞书是并列的两条通道,不是替代关系。 飞书是「现在就有人看见」,邮件是「事后能翻到、能转发」。更关键的是它们的故障面不重叠,飞书挂了(换 token 失败、频控、应用被停用)邮件照发,SMTP 挂了飞书照发。告警系统最怕的就是出事的时候它自己也哑了,多一条独立通道比把单条做得更可靠划算。

卡片里必须有堆栈。 这是我第一版做漏的,当时卡片只有一行 位置: app:///src/app/(web)/(routes)/blog/page.tsx,看到告警还得去开 Sentry,那这条推送的价值就打了对折。

# 二、20 分钟跑通最小可用版本

下面这套是我实际跑通的顺序,环境是 Next.js 16.3.1 + @sentry/nextjs 10.74.0 + Node 22.23.1,output: 'standalone',部署在腾讯云 Docker 容器里。

# Step 1:装包,注意 yarn 缓存这个坑

yarn add @sentry/nextjs
@前端进阶之旅: 代码已经复制到剪贴板

我第一次跑这条命令,终端最后打了 exit code 0,看起来是成功的。结果 node_modules/@sentry/nextjs 根本不存在,package.json 里也没有这一行。回头翻完整日志才看到藏在一堆 peer dependency warning 后面的这句:

error Error: ENOENT: no such file or directory, open ‘/Users/mac/Library/Caches/Yarn/v6/npm-@sentry-cli-darwin-2.58.6-…/node_modules/@sentry/cli-darwin/.yarn-metadata.json’

@sentry/cli-darwin 在 yarn 全局缓存里的那份元数据坏了。yarn 1.x 在这种情况下会报错但仍然退出 0,这个组合非常坑,你以为装好了其实什么都没发生。清掉那一份缓存重来就好:

rm -rf "$HOME/Library/Caches/Yarn/v6/npm-@sentry-cli-darwin-2.58.6-"*
yarn add @sentry/nextjs
@前端进阶之旅: 代码已经复制到剪贴板

装完记得 node -p "require('@sentry/nextjs/package.json').version" 确认一下,别只看 exit code。

# Step 2:三端接线,文件位置不能放错

@sentry/nextjs 在 App Router 下有三个入口,位置是 Next 规定的,放错地方不会报错,只会静默不生效,这一点很要命。

浏览器端是 src/instrumentation-client.ts(和 src/app 同级):

import * as Sentry from '@sentry/nextjs'

import { initSentryClient } from '@/libs/sentry'

initSentryClient()

// 不接这个的话,App Router 的软导航在 Sentry 里完全看不到,
// 所有页面的性能数据都会算到首次进站的那个 pageload 上
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart
@前端进阶之旅: 代码已经复制到剪贴板

服务端和 edge 各自是仓库根目录的 sentry.server.config.ts / sentry.edge.config.ts,由 src/instrumentation.ts 按 runtime 动态加载:

export async function register() {
  // 必须动态 import:这两个配置文件分别只在对应 runtime 下可用,
  // server 那份会拉进 node: 内置模块,顶层引会让 edge 编译直接失败
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('../sentry.server.config')
  }
  if (process.env.NEXT_RUNTIME === 'edge') {
    await import('../sentry.edge.config')
  }
  // …原有逻辑
}

// Next 15+ 提供的服务端请求出错钩子,RSC 和 route handler 都走它。
// 名字是 Next 约定的,不能改、也不能包一层
export const onRequestError = Sentry.captureRequestError
@前端进阶之旅: 代码已经复制到剪贴板

onRequestError 是我觉得最值回票价的一个。接上之前,服务端渲染阶段抛的错只有容器 stdout 知道;接上之后它和浏览器端错误进同一个项目,按 runtime tag 区分。

# Step 3:包一层再给业务用

我没让业务代码直接 from '@sentry/nextjs',而是全部收到 @/libs/sentry 后面。这层隔离换来两件事:未 init 时所有调用自动 no-op,业务侧一行防御都不用写;所有出站数据都过同一份脱敏规则,不会有哪条路径漏掉。

function isInitialized(): boolean {
  try {
    return !!Sentry.getClient()
  } catch {
    return false
  }
}

export function captureException(error: unknown, options?: CaptureOptions): void {
  if (!isInitialized()) return
  Sentry.captureException(error, { tags: options?.tags, level: options?.level ?? 'error' })
}
@前端进阶之旅: 代码已经复制到剪贴板

然后把三层错误边界都接上:路由级 error.tsx、根级 global-error.tsx、组件级 ErrorBoundary。三者打不同的 boundary tag,因为严重程度差很远,global 意味着整站白屏,component 只是黑掉一块。

error.tsx 里有个细节值得单独说:生产构建下服务端错误的 message 会被 Next 抹掉,只留一个 digest。不把它带上的话,Sentry 上就是一条光秃秃的 An error occurred in the Server Components render,等于没有信息。

useEffect(() => {
  captureException(error, {
    tags: { boundary: 'route', ...(error?.digest ? { digest: error.digest } : {}) }
  })
}, [error])
@前端进阶之旅: 代码已经复制到剪贴板

# 三、为什么本地 yarn dev 收不到任何上报

fe
  • 一、整体架构
  • 二、20 分钟跑通最小可用版本
    • Step 1:装包,注意 yarn 缓存这个坑
    • Step 2:三端接线,文件位置不能放错
    • Step 3:包一层再给业务用
  • 三、为什么本地 yarn dev 收不到任何上报
  • 四、脱敏这层不能省,我是怎么验的
    • 不发一条数据给 Sentry,怎么证明脱敏生效了
  • 五、Sentry 的调用栈是反的
  • 六、webhook 路由完整实现,以及为什么你的验签一直失败
    • 第二步,把 payload 拍平成一个摘要
    • 第三步,拼卡片推出去
    • 怎么在本地验这整条
  • 七、集成建好了,却在告警规则里选不到
  • 八、飞书自建应用怎么配,以及那个「配了不生效」的坑
    • 先给应用加「机器人」能力
    • 权限用批量导入,别一个个点
    • 要双向交互才需要订阅事件
    • 最容易翻车的一步:配完必须创建版本并发布
  • 九、上线后那两次误判
  • 十、上生产前的 checklist
  • 总结
  • 参考

别再手写 FLIP 了,React 把页面动画交给了浏览器 →