Front-End-Checklist 实战:在生产环境集成实时错误监控(Sentry + Next.js App Router)
2026/9/19 21:06:25 网站建设 项目流程

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.tsNode.js 服务端捕获 API 路由、Server Component 与渲染错误
apps/web/sentry.edge.config.tsEdge 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_ENVIRONMENTNODE_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 }) : botProtectedConfig
  • authToken / 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, }) })

unhandledrejectionevent.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 环境按顺序验证:

  1. 事件可达性:主动抛出throw new Error('Test monitoring'),确认事件在 30 秒内出现在监控面板;
  2. 事件完整性:检查事件是否包含正确的release标签、environment与用户上下文;
  3. Source Map 还原:确认 CI 部署钩子已上传 source maps,堆栈显示原始 TypeScript 行号而非压缩产物(对应仓库withSentryConfig的构建期配置);
  4. 告警存在性:确认至少有一条"生产新错误"告警规则并路由到 on-call 频道;
  5. 失败请求记录:强制制造一次失败的 API 响应,确认系统记录了请求失败的路由与状态码;
  6. 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询