Front-End-Checklist 实战:在生产环境集成实时错误监控(Sentry + Next.js App Router)
【免费下载链接】Front-End-Checklist🗂 The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist
本文以 Front-End-Checklist 仓库中 error-monitoring 规则 为核心骨架,结合仓库内
apps/web的真实 Sentry 接入实现,系统讲解如何在生产环境捕获未捕获异常、Promise 拒绝、组件级崩溃、失败网络请求与核心 Web 指标(RUM),并配置分级告警。读完本文,你将能独立完成一套"事件采集 → 脱敏上报 → 版本追踪 → 告警路由"的生产可观测闭环,并掌握 MTTD(平均检测时间)从数天缩短到分钟级别的工程方法。
什么是实时错误监控:打通生产环境与研发团队的反馈闭环
在没有错误监控的情况下,生产环境对团队是"黑盒":用户不会提交详细的 bug 报告,他们只会默默离开。实时错误监控服务的价值在于——捕获、分组并对未捕获异常与 Promise 拒绝进行告警,让问题在用户报告之前就被发现。正如 rule.md 开篇所定义的:错误监控服务在问题发生的几秒内给出可操作的堆栈、面包屑(breadcrumbs)和用户上下文,把 MTTD 从"数天"压缩到"数分钟"。
这条规则在 Front-End-Checklist 仓库中被归类为testing类别下的最佳实践,优先级为high(高)、难度intermediate(中级)、预估耗时30 分钟,其元数据见 packages/content/rules/en/testing/error-monitoring.mdx 的 frontmatter。配套的 skills/error-monitoring/SKILL.md 则将它包装为 Agent 可执行的技能,涵盖 Check(检查是否已接入监控)、Fix(修复缺失的监控能力)、Explain(解释监控服务原理)、Code Review(审查监控初始化代码)四个动作。
安装与初始化:在应用入口尽早挂载监控 SDK
规则给出的安装命令是:
pnpm add @sentry/nextjs # 或纯 React 项目: pnpm add @sentry/react仓库apps/web采用 pnpm workspace + Next.js App Router 架构,实际使用了@sentry/nextjs包。安装后,Next.js 的接入分为客户端与**服务端(含 Edge)**两套独立配置,仓库中三份真实配置文件的职责划分如下:
| 配置文件 | 运行时 | 职责 |
|---|---|---|
| apps/web/sentry.client.config.ts | 浏览器 | 初始化浏览器端错误、RUM 与 Session Replay |
| apps/web/sentry.server.config.ts | Node.js 服务端 | 捕获 API 路由、Server Component 与渲染错误 |
| apps/web/sentry.edge.config.ts | Edge Runtime | 捕获边缘函数中的异常 |
关键的加载枢纽是 apps/web/instrumentation.ts,它通过 Next.js 的register()钩子按运行时条件动态加载对应配置:
import * as Sentry from '@sentry/nextjs' export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { await import('./sentry.server.config') } if (process.env.NEXT_RUNTIME === 'edge') { await import('./sentry.edge.config') } } export const onRequestError = Sentry.captureRequestError其中onRequestError是 Next.js 16 新增的请求错误钩子,直接委托给Sentry.captureRequestError,意味着任何服务端请求处理过程中的未捕获异常都会自动进入 Sentry,无需在每个 API 路由中手动包裹 try-catch。
Next.js(App Router)初始化配置详解
规则文档给出了客户端与服务端的初始化骨架,下面逐参数拆解其含义:
// sentry.client.config.ts Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, // 只在生产环境启用——避免开发环境噪音 enabled: process.env.NODE_ENV === 'production', // 给每个事件打上部署版本标签,用于发布追踪 release: process.env.NEXT_PUBLIC_APP_VERSION, environment: process.env.NEXT_PUBLIC_ENVIRONMENT ?? 'production', // 采样率:1.0 = 捕获 100% 的错误(推荐从全量开始) sampleRate: 1.0, // 性能监控——对 10% 的事务进行采样 tracesSampleRate: 0.1, // 会话回放——采样 10% 的会话,100% 的出错会话 replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, integrations: [ Sentry.replayIntegration({ // 遮罩所有文本和输入框,满足隐私合规要求 maskAllText: true, blockAllMedia: true, }), ], beforeSend(event, hint) { // 在错误事件离开浏览器之前剥离 PII(个人身份信息) if (event.user?.email) { event.user.email = '[Filtered]' } return event }, })// sentry.server.config.ts Sentry.init({ dsn: process.env.SENTRY_DSN, enabled: process.env.NODE_ENV === 'production', release: process.env.APP_VERSION, environment: process.env.ENVIRONMENT ?? 'production', sampleRate: 1.0, tracesSampleRate: 0.05, })对照仓库真实实现,apps/web/sentry.server.config.ts 给出了更稳健的工程化写法,值得逐条对照学习:
import * as Sentry from '@sentry/nextjs' const dsn = process.env.SENTRY_DSN?.trim() || process.env.NEXT_PUBLIC_SENTRY_DSN?.trim() Sentry.init({ dsn, enabled: Boolean(dsn) && process.env.NODE_ENV === 'production', environment: process.env.NEXT_PUBLIC_ENVIRONMENT ?? process.env.NODE_ENV ?? 'development', release: process.env.NEXT_PUBLIC_APP_VERSION, sendDefaultPii: false, tracesSampleRate: 0.1 })几个关键差异点体现了实战经验:
- DSN 双环境兼容:
process.env.SENTRY_DSN?.trim() || process.env.NEXT_PUBLIC_SENTRY_DSN?.trim(),同时兼容服务端私有变量与客户端公开变量,且trim()防止误配置的空白字符导致 DSN 校验失败; enabled双重保险:Boolean(dsn) && process.env.NODE_ENV === 'production'——不仅要求生产环境,还要求 DSN 真实存在,避免未配置 DSN 时 SDK 空转或产生噪音;sendDefaultPii: false:从源头禁止默认发送个人身份信息,与规则文档中beforeSend手工脱敏形成"双保险";- 环境变量回退链:
environment依次回退NEXT_PUBLIC_ENVIRONMENT→NODE_ENV→'development',保证任何环境下事件都有环境标记。
客户端配置 apps/web/instrumentation-client.ts 与 apps/web/sentry.edge.config.ts 遵循同样的模式,此外客户端还导出了onRouterTransitionStart = Sentry.captureRouterTransitionStart,用于在路由切换开始时采集性能事务。
构建期接入:withSentryConfig 与 Source Map 上传
仅靠运行时初始化还不够,生产环境还需要在构建期完成两件事:注入 Sentry Webpack 插件与上传 Source Map。仓库在 apps/web/next.config.js 中实现了条件化包装:
const sentryWrappedConfig = process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT ? withSentryConfig(botProtectedConfig, { authToken: process.env.SENTRY_AUTH_TOKEN, org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, silent: !process.env.CI, tunnelRoute: '/monitoring', webpack: { treeshake: { removeDebugLogging: true } }, widenClientFileUpload: true }) : botProtectedConfigauthToken / org / project齐全时才启用:缺少任一凭据则跳过包装,保证本地开发或 CI 未配置凭据时构建不被破坏;tunnelRoute: '/monitoring':将浏览器端事件通过同源隧道转发,规避广告拦截器(如 uBlock)对 Sentry 上报域名的拦截,同时绕开 CSP 中connect-src 'self'的限制——注意 next.config.js 中 CSP 的connect-src确实只允许'self',隧道方案与安全策略完全自洽;widenClientFileUpload: true:扩大客户端文件上传范围,提高 Source Map 还原准确性;treeshake.removeDebugLogging:生产构建时剔除调试日志,减少包体积与噪音。
这正是验证清单第 3 条("CI 部署钩子上传 source maps,让堆栈显示原始 TypeScript 行号而非压缩产物")的落地保障。
用户识别:用不透明 ID 关联具体账号
错误监控的价值之一是把错误追溯到具体账号。规则强调:登录成功后挂载用户上下文,登出时清除,并且 ID 必须使用不透明 ID(opaque ID),绝不能使用邮箱或姓名:
// 登录成功后 function onUserLogin(user: { id: string; plan: string }) { Sentry.setUser({ id: user.id, // 使用不透明 ID,绝不用 email/name plan: user.plan, // 非 PII 属性有助于快速分诊 }) } function onUserLogout() { Sentry.setUser(null) }plan这类非敏感属性可以保留,因为它在分诊时能直接回答"受影响的是哪类用户"。仓库中 apps/web/lib/telemetry-server.ts 的captureServerException也遵循同一原则,仅在显式传入context.userId时才附加user: { id: context.userId }:
Sentry.captureException(normalizedError, { tags: { 'app.feature': 'api', 'app.route': context.route, ...(context.tags ?? {}) }, extra: context.extra, ...(context.userId ? { user: { id: context.userId } } : {}) })同时该函数还体现了一个优秀模式:错误规范化——用error instanceof Error ? error : new Error(String(error))把未知的 thrown 值统一为Error实例,避免上报畸形对象。
React Error Boundary:把组件级崩溃变成可恢复的 UI
框架级错误边界是监控体系中"组件层崩溃"的唯一捕获点。规则文档给出了基于 Sentry 的错误边界模式,其核心结构为:
// components/error-boundary.tsx interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean eventId: string | null } state: State = { hasError: false, eventId: null } static getDerivedStateFromError(): Partial<State> { /* ... */ } // 捕获错误后渲染回退 UI,并通过 // Sentry.showReportDialog({ eventId: this.state.eventId ?? '' }) // 提供"Report feedback"按钮,让用户直接反馈,形成互动闭环其设计要点包括:用getDerivedStateFromError标记出错状态;用Sentry.captureException上报并把返回的eventId存入 State;回退 UI 中提供"Report feedback"按钮触发Sentry.showReportDialog,让用户直接对具体错误事件提交反馈。
仓库在 Next.js App Router 中实践了这一模式的两个层级:
apps/web/app/(site)/error.tsx/error.tsx)是路由级错误边界(带'use client'指令),出错后在useEffect中调用Sentry.captureException(error),同时渲染 500 回退 UI、展示error.digest错误 ID 和"Try again"重试按钮(调用reset()),并带有role="alert"与aria-live="polite"的无障碍声明:
useEffect(() => { Sentry.captureException(error) console.error('[ErrorPage]', error) }, [error])apps/web/app/global-error.tsx则是兜底的全局错误边界——它必须自己渲染<html>和<body>(因为根布局已崩溃),同样在useEffect中上报,并使用aria-live="assertive"提升播报优先级。两层边界缺一不可:路由级边界恢复时根布局仍存活,而全局边界确保最坏情况下错误依然被捕获上报。
捕获自定义错误:业务逻辑异常显式上报
并非所有错误都会"自然"进入监控——业务逻辑违规需要显式捕获。规则文档给出的标准范式是:在 try-catch 中使用withScope附加上下文并captureException,然后必须重新抛出,绝不静默吞错:
// 捕获特定错误并附带额外上下文 async function processPayment(orderId: string, amount: number) { try { await paymentGateway.charge({ orderId, amount }) } catch (error) { Sentry.withScope((scope) => { scope.setTag('feature', 'checkout') scope.setContext('order', { orderId, amount }) scope.setLevel('error') Sentry.captureException(error) }) throw error // 重新抛出——绝不静默吞掉错误 } } // 记录一条非异常消息,用于重要事件 Sentry.captureMessage('Payment gateway returned unexpected status code 202', 'warning')参数说明:
scope.setTag('feature', 'checkout'):打上功能标签,便于按业务模块筛选与聚合;scope.setContext('order', { orderId, amount }):附加业务上下文,复现问题时直接可见订单维度数据;scope.setLevel('error'):显式指定级别,区别于captureMessage的'warning';captureMessage(message, level):适用于"这不是异常但值得关注"的事件(如第三方返回了非预期状态码)。
仓库中的 apps/web/lib/telemetry-server.ts 正是这一范式的生产级变体:trackServerEvent在 OpenPanel 追踪失败时,把失败原因本身作为异常捕获进 Sentry,并附带app.feature: 'telemetry'、app.telemetry_event: event标签与原始事件属性:
void opServer.track(event, properties).catch(error => { if (!sentryDsn) { return } Sentry.captureException(error, { tags: { 'app.feature': 'telemetry', 'app.telemetry_event': event }, extra: properties }) })注意这里的if (!sentryDsn) return——没有 DSN 就不做任何事,避免在未接入监控的环境里产生额外开销。
为什么必须监控:MTTD 与事故成本
规则文档的"Why It Matters"部分给出了最直接的商业论证:用户几乎不会提交详细的 bug 报告,他们只会流失。监控带来的是:
- 可操作的堆栈(stack traces);
- 事件发生前的面包屑(breadcrumbs,用户操作轨迹);
- 用户上下文(账号、套餐、环境、版本)。
这些数据在问题发生后几秒内可用,将 MTTD 从"数天"缩短到"数分钟",并大幅降低事故处理成本。这与 SKILL.md 中"用错误分组(error grouping)减少告警噪音"的解释要点一致:监控服务会把相同堆栈的错误自动聚合为一个 Issue,团队处理的不是"10 万条日志"而是"几十个问题"。
监控什么:六类核心信号
规则文档用表格完整列出了应监控的信号:
| 信号 | 说明 |
|---|---|
| 未捕获异常(Unhandled exceptions) | 未被任何 try-catch 捕获的 JavaScript 错误 |
| 未捕获的 Promise 拒绝(Unhandled promise rejections) | 冒泡到全局处理器的异步错误 |
| 框架错误边界(Framework error boundaries) | React、Vue、Angular 组件级崩溃 |
| 网络错误(Network errors) | 失败的 API 调用与 HTTP 4xx/5xx 响应 |
| 自定义错误(Custom errors) | 你显式捕获的业务逻辑违规 |
| 性能异常(Performance anomalies) | 核心 Web 指标(Core Web Vitals)回归、慢事务 |
其中"框架错误边界"对应上文的两层 Error Boundary,"自定义错误"对应captureException/captureMessage显式上报,"未捕获异常与 Promise 拒绝"在 Sentry 客户端 SDK 初始化后即自动接管,无需手写。
生产 RUM 与失败请求:用户感知的回归信号
规则文档特别强调:用户可见的回归往往先表现为"交互变慢"或"API 失败爆发",而不是顶层崩溃。因此必须同时捕获 RUM(Real User Monitoring)与失败请求:
function reportWebVital(name: string, value: number) { monitoring.captureMessage(`web-vital:${name}`, 'info', { tags: { source: 'rum' }, extra: { value }, }) } onLCP((metric) => reportWebVital('LCP', metric.value)) onCLS((metric) => reportWebVital('CLS', metric.value)) onINP((metric) => reportWebVital('INP', metric.value)) // 包装 fetch,对非 2xx 响应主动上报 async function monitoredFetch(input: RequestInfo | URL, init?: RequestInit) { const response = await fetch(input, init) if (!response.ok) { monitoring.captureMessage('frontend-network-failure', 'warning', { tags: { status: String(response.status) }, extra: { url: String(input) }, }) } return response }这套思路与规则文档引用的参考标准web.dev: Monitor and analyze the app一脉相承:LCP(最大内容绘制)、CLS(累积布局偏移)、INP(交互到下一次绘制)这三个指标直接决定用户对"快不快、稳不稳"的体感。实现上,仓库依赖tracesSampleRate: 0.1的初始化配置来支撑性能事务采集;对于失败请求,规则提供的monitoredFetch包装模式可以在不引入 SDK 自动仪器化的情况下,精确记录"哪个路由、什么状态码"。
备选方案:不引入 SDK 时的全局错误处理器
对于不打算接入完整监控 SDK 的应用,规则给出了最低限度的兜底方案——在浏览器全局挂两个事件监听器:
// 未捕获异常 window.addEventListener('error', (event) => { sendToMonitoring({ type: 'exception', message: event.message, source: event.filename, line: event.lineno, column: event.colno, stack: event.error?.stack, }) }) // 未捕获的 Promise 拒绝 window.addEventListener('unhandledrejection', (event) => { sendToMonitoring({ type: 'unhandledRejection', reason: String(event.reason), stack: event.reason instanceof Error ? event.reason.stack : undefined, }) })unhandledrejection中event.reason instanceof Error的守卫非常重要——被拒绝的值不一定是 Error 实例(可能是字符串或对象),需要显式String()化并仅在确实存在堆栈时上报。这套方案能覆盖"未捕获异常 + Promise 拒绝"两类信号,但无法获得面包屑、用户上下文、Source Map 还原与错误分组能力,仅适合作为过渡方案。
告警配置:页对的人,不制造噪音
规则文档给出了完整的告警规则矩阵,这是监控体系从"看得见"到"响得快"的关键一步:
| 规则 | 阈值 | 通道 |
|---|---|---|
| 新错误(从未出现过) | 立即 | Slack #engineering |
| 错误率激增 | 较前一小时 +200% | PagerDuty(on-call) |
| 关键未捕获异常 | 生产环境任意出现 | Slack + 邮件 |
| 问题回归(已修复→复发) | 立即 | Slack + 指派负责人 |
| LCP/INP 回归 | 持续突破基线 | 性能告警专用频道 |
| 失败请求激增 | 超出每个端点的正常基线 | On-call 或 API 负责人 |
设计要点是分级路由:新错误只进工程 Slack(低噪音),错误率激增才唤起 PagerDuty,回归问题直接指派给原负责人。这样既能保证关键问题必然触达 on-call,又避免把每一个错误都变成寻呼。
规则同时警告了一个高频坑:在开发环境初始化 SDK 会产生噪音并耗尽事件配额。必须用process.env.NODE_ENV === 'production'守卫,或者为每个环境使用独立 DSN,否则 staging 的错误会错误地唤起生产 on-call。仓库对这一点执行得非常彻底——三份配置文件(client/server/edge)全部使用enabled: Boolean(dsn) && process.env.NODE_ENV === 'production'。
验收标准:六步验证监控闭环
规则文档给出了一套可直接执行的验收清单,建议在 staging 环境按顺序验证:
- 事件可达性:主动抛出
throw new Error('Test monitoring'),确认事件在 30 秒内出现在监控面板; - 事件完整性:检查事件是否包含正确的
release标签、environment与用户上下文; - Source Map 还原:确认 CI 部署钩子已上传 source maps,堆栈显示原始 TypeScript 行号而非压缩产物(对应仓库
withSentryConfig的构建期配置); - 告警存在性:确认至少有一条"生产新错误"告警规则并路由到 on-call 频道;
- 失败请求记录:强制制造一次失败的 API 响应,确认系统记录了请求失败的路由与状态码;
- RUM 覆盖:确认生产 RUM 捕获 LCP、CLS 或 INP,且告警基于回归阈值而非原始日志。
验收通过后,对照规则文档的Standards部分做最终核验:将实现与Sentry: Getting started with JavaScript官方接入指南核对,再与web.dev: Monitor and analyze the app核对监控覆盖是否完整。
在仓库中进一步探索
如果你想看到这套监控体系在真实大型应用中的完整形态,建议按以下顺序阅读仓库源码:
- apps/web/instrumentation.ts — 运行时注册枢纽,按 Node/Edge 分流加载配置;
- apps/web/sentry.server.config.ts、apps/web/sentry.edge.config.ts、apps/web/instrumentation-client.ts — 三套运行时初始化配置;
- apps/web/next.config.js —
withSentryConfig条件化包装与/monitoring隧道、Source Map 上传; - apps/web/app/(site)/error.tsx/error.tsx)、apps/web/app/global-error.tsx — 路由级与全局双层错误边界;
- apps/web/lib/telemetry-server.ts —
captureServerException的服务端错误规范化与标签化上报范式; - packages/content/rules/en/testing/error-monitoring.mdx — 该规则的结构化内容源(含 tldr、whyItMatters、aiContext 与 relatedRules);
- skills/error-monitoring/SKILL.md — 面向 Agent 的 Check/Fix/Explain/Code Review 四段式执行指引。
核心要义回顾:实时错误监控是生产环境与工程团队之间唯一的反馈闭环——早期接入 SDK、全量采集错误、显式脱敏 PII、打上 release/environment 标签、按严重度分级告警、用 RUM 捕获用户体感退化,最终用六步验收清单确认整条链路真实可用。遵循 skills/error-monitoring/references/rule.md 的标准,你的应用就能做到"在用户开口之前发现并修复问题"。
【免费下载链接】Front-End-Checklist🗂 The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考