第二道防线是可观测:开发时问题看得清、生产上问题藏不住。本节前半讲 DevTools 工具箱与水合错误定位法(第 4 章理论的实操版),后半讲生产错误捕获与上报闭环——让用户撞到的问题,在你看到报表时就已有修复在路上。
Nuxt DevTools 是官方的可视化面板,dev 模式下底部图标呼出:
// nuxt.config.ts:默认可用,显式开启确保体验完整 export default defineNuxtConfig({ devtools: { enabled: true }, })
高频用途四项:
排水合错误时的定位流程(4.1 的三类成因实操化):
<template> <!-- 典型案例:评论列表的相对时间 --> <!-- 错误写法:服务端说"3 秒前",浏览器算出"4 秒前" --> <!-- <span>{{ relativeTime(comment.ts) }}</span> --> <!-- 修复:初始渲染用绝对时间(两端一致),水合后再切相对时间 --> <span>{{ hydrated ? relativeTime(comment.ts) : formatDate(comment.ts) }}</span> </template> <script setup> const hydrated = ref(false) onMounted(() => { hydrated.value = true }) // 只在浏览器翻转 </script>
线上没有控制台,错误要主动抓。Nuxt 的错误分三个来源,各设哨位:
哨位一:客户端运行时错误。vueApp.config.errorHandler 加 unhandledrejection 监听,插件里统一装:
// plugins/error-observer.client.ts export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('app:error', (err) => { reportError({ kind: 'app', message: err.message, stack: err.stack }) }) window.addEventListener('unhandledrejection', (e) => { reportError({ kind: 'promise', message: String(e.reason) }) }) }) // 上报通道:发给自己第6章写的端点(同源、零依赖) function reportError(payload: Record<string, unknown>) { $fetch('/api/client-errors', { method: 'POST', body: { ...payload, url: location.href, ts: Date.now() }, }).catch(() => {}) }
哨位二:服务端错误。Nitro 的钩子兜住渲染期与接口期的异常:
// server/plugins/error-track.ts export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('error', (error, { event }) => { console.error('[server-error]', event?.path, error.message) // 结构化日志进收集管道(stdout 由平台日志系统接管) }) })
哨位三:用户手动的"它坏了"按钮。错误页(2.3 的 error.vue)上放反馈入口,附上错误码与页面地址——用户报告是免费的高价值样本。
捕获只是开始,闭环才是目的:
错误发生 → 哨位捕获 → 上报入库 → 聚类去重(同栈算一条) → 阈值告警(新错误/突增)→ 建议题 → 修复上线 → 曲线回落确认
自建最小方案:6.2 的 storage 或一张数据库表存错误,按"错误指纹"(栈首行哈希)聚类,每日汇总。规模上来后接专业平台(Sentry 等)——它们多提供 Nuxt 集成模块,按 7.3 的评估流程引入。
日志的工程纪律(服务端日志进不了浏览器控制台,是排查的主料):
8.3 提过实验室测量的盲区:真实用户的设备与网络千差万别。RUM 在真实页面加载时采集三指标与接口耗时上报:
// plugins/rum.client.ts:用标准 Web API 采集,零依赖起步 export default defineNuxtPlugin(() => { onMounted(() => { // 性能时间线里的 LCP 候选 new PerformanceObserver((list) => { const entries = list.getEntries() const last = entries[entries.length - 1] report({ metric: 'lcp', value: last.startTime }) }).observe({ type: 'largest-contentful-paint', buffered: true }) }) })
采集到的分布(P75 分位是行业口径)比单次 Lighthouse 分数更接近真相:实验室九十分、真实用户 P75 四秒的站点,问题出在真实网络的某个环节(首字节慢?字体阻塞?),RUM 数据配合 8.1 的关键路径分析法收敛病因。
⚠️ 常见坑:错误上报本身抛错。上报通道故障引发新错误、新错误再触发上报,雪崩式刷量。上报函数必须自我兜底(catch 一切)并做采样与限流。
💡 关键直觉:可观测的投入产出比随用户量放大——十个用户时人肉复现还行,十万用户时"错误聚类报表 + P75 曲线"是你唯一能看见全貌的窗口。