前言
在生产环境调试 Next.js 应用时,开发者常常面临三大挑战:
- 混合渲染模式下客户端异常难以定位
- 用户环境信息(设备、网络、存储等)收集困难
- 偶现问题因缺乏现场数据难以复现
本文将通过集成 PageSpy ,实现以下调试能力增强:
- 🎯 全生命周期错误追踪(CSR/SSR)
- 📊 网络请求瀑布流分析
- ⏪ 用户操作轨迹回放(基于 rrweb)
- 📥 离线日志缓存与上传
我们开始吧!
一、前置条件
方案选型对比
| 集成方式 | 适用场景 | 包体积增量 | 可维护性 |
|---|---|---|---|
| Npm 包 | 插件扩展、可读性高 | 较大 | 高 |
| Script 注入 | 快速接入、对于SSR收集更及时(更早)、轻量级调试 | 较小 | 中 |
二、集成方案
方案一:Npm 包集成
步骤 1:安装核心依赖
# 核心库
npm install @huolala-tech/page-spy-browser
# 离线日志插件(可选)
npm install @huolala-tech/page-spy-plugin-data-harbor
# 用户行为录制插件(可选)
npm install @huolala-tech/page-spy-plugin-rrweb
步骤 2:初始化配置
import { AppProps } from 'next/app';
import './app.css';
import DebugPackage from './DebugPackage';
const DEBUGS = ['local', 'dev', 'development']
function MyApp(appProps: AppProps) {
const { Component, pageProps } = appProps
const isDebug = DEBUGS.includes(process.env.NEXT_NODE_ENV) || Boolean(pageProps?.data?.debug)
return <>
<DebugPackage isDebug={isDebug} />
<Component {...pageProps} />
</>
}
export default MyApp
'use client';
import { useEffect, useRef } from 'react';
/**
* 异步按需加载页面监控脚本
* @description 该脚本用于加载页面监控的相关插件
* 1. DataHarborPlugin: 缓存离线日志数据,提供下载 / 上传日志功能
* 2. RRWebPlugin: 将用户操作轨迹记录到离线日志中
* 3. PageSpy: 页面监控插件
* 4. PageSpy.registerPlugin: 注册插件
*/
export default function DebugPackage({ isDebug }: { isDebug: boolean }) {
const pageSpyRef = useRef(null);
const vConsoleRef = useRef(null);
useEffect(() => {
if (isDebug) {
Promise.all([
import('@huolala-tech/page-spy-browser'),
import('@huolala-tech/page-spy-plugin-data-harbor'),
import('@huolala-tech/page-spy-plugin-rrweb')
]).then(([PageSpy, { default: DataHarborPlugin }, { default: RRWebPlugin }]) => {
// 初始化插件(必须早于"初始化PageSpy"之前初始化&注册插件,否则插件会失效)
const harbor = new DataHarborPlugin({
maximum: 1 * 1024 * 1024
});
const rrweb = new RRWebPlugin();
// 注册插件
[harbor, rrweb].forEach(p => {
PageSpy.default.registerPlugin(p);
});
// 初始化PageSpy
// eslint-disable-next-line new-cap
pageSpyRef.current = new PageSpy.default({
api: '你的服务地址',
clientOrigin: `${window.location.protocol}//你的服务地址`,
project: '你的项目名称',
autoRender: isDebug,
title: '你的标题'
});
});
}
return () => {
pageSpyRef.current?.abort();
};
}, [isDebug]);
return null;
}
方案二:Script注入(推荐)
import { AppProps } from 'next/app';
import Head from 'next/head';
import './app.css';
const DEBUGS = ['local', 'dev', 'development']
function MyApp(appProps: AppProps) {
const { Component, pageProps } = appProps
const isDebug = DEBUGS.includes(process.env.NEXT_NODE_ENV) || Boolean(pageProps?.data?.debug)
return <>
<Head>
{/* 尽可能早的加载PageSpy进行页面捕捉 */}
{isDebug && (<>
<script crossOrigin="anonymous" src="//你的服务地址/page-spy/index.min.js" />
<script crossOrigin="anonymous" src="//你的服务地址/plugin/data-harbor/index.min.js" />
<script crossOrigin="anonymous" src="//你的服务地址/plugin/rrweb/index.min.js" />
<script dangerouslySetInnerHTML={{
__html: `
(function (w, __d, __s) {
if(!w.$harbor) { w.$harbor = new w.DataHarborPlugin({ maximum: 1 * 1024 * 1024 }); }
if(!w.$rrweb) { w.$rrweb = new w.RRWebPlugin(); }
[w.$harbor, w.$rrweb].forEach(p => { w.PageSpy.registerPlugin(p); })
w.$pageSpy = new w.PageSpy({
api: '你的服务地址',
title: w.location.href,
project: '你的项目名称',
autoRender: true
});
})(window,document,"script")
`
}} />
</>)}
<meta charSet="utf-8" />
<title>在 Next.js 里集成 PageSpy:打造远程调试利器</title>
...其它
</Head>
<Component {...pageProps} />
</>
}
export default MyApp
三、其它方案
'use client';
import Script from 'next/script';
/**
* 异步按需加载页面监控脚本,直接使用了Script的注入方式
* @description 一、该脚本用于加载页面监控的相关插件
* 1. DataHarborPlugin: 缓存离线日志数据,提供下载 / 上传日志功能
* 2. RRWebPlugin: 将用户操作轨迹记录到离线日志中
* 3. PageSpy: 页面监控插件
* 4. PageSpy.registerPlugin: 注册插件
* @description 二、PageSpy纯离线方案(更多细节可关注官网)
* @description 三、该脚本用于加载vconsole调试工具
*/
export default function DebugScript({ isDebug }: { isDebug: boolean }) {
return (<>
<Script
src="//你的服务地址/plugin/data-harbor/index.min.js"
// strategy="lazyOnload"
onLoad={() => {
window.$harbor = new window.DataHarborPlugin({
maximum: 1 * 1024 * 1024 // 内存中的数据记录达到 1M 时写入临时文件
});
}}
/>
<Script
src="//你的服务地址/plugin/rrweb/index.min.js"
// strategy="lazyOnload"
onLoad={() => {
window.$rrweb = new window.RRWebPlugin();
}}
/>
<Script
src="//你的服务地址/page-spy/index.min.js"
// strategy="lazyOnload"
onLoad={() => {
[window.$harbor, window.$rrweb].forEach(p => {
window.PageSpy.registerPlugin(p);
})
window.$pageSpy = new window.PageSpy({
api: '你的服务地址',
clientOrigin: `${window.location.protocol}//你的服务地址`,
project: '你的项目名称',
autoRender: isDebug,
title: window.location.href
});
}}
/>
{/* 纯离线方案 */}
{/* <Script
src="https://unpkg.com/@huolala-tech/page-spy-plugin-ospy"
strategy="lazyOnload"
onLoad={() => {
window.$oSpy = new window.OSpy({
// api: '你的服务地址',
// project: '你的项目名称',
title: '离线日志',
autoRender: isDebug
});
}}
/> */}
{
isDebug && (
<Script
src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"
// strategy="lazyOnload"
onLoad={() => {
window.vConsole = new window.VConsole();
setTimeout(() => {
const vcId = document.getElementById('__vconsole')
// eslint-disable-next-line max-nested-callbacks
vcId?.addEventListener('click', (e) => e.stopPropagation())
if (vcId) {
vcId.style.position = 'relative'
vcId.style.zIndex = 1000000
}
}, 1000)
}}
/>
)
}
</>);
}