{"_id":"@arms/rum-browser-nextjs","_rev":"2-70a4f0fdfd4411b9281cdb243fa5cc96","name":"@arms/rum-browser-nextjs","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@arms/rum-browser-nextjs","version":"0.1.0","keywords":["rum","real-user-monitoring","arms","nextjs","next","react","app-router","pages-router","error-tracking","monitoring"],"author":{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},"license":"ISC","_id":"@arms/rum-browser-nextjs@0.1.0","maintainers":[{"name":"fengsir","email":"fengsir@live.com"},{"name":"yunjin4","email":"xiashujin.xsj@alibaba-inc.com"},{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},{"name":"yangling1996","email":"yl260087@alibaba-inc.com"},{"name":"yunyi-alibaba","email":"ly403664@alibaba-inc.com"},{"name":"alibaba_rum_harmony","email":"yanglanxin.ylx@alibaba-inc.com"}],"bin":{"rum-nextjs-init":"bin/rum-nextjs-init.mjs"},"dist":{"shasum":"2c205b0b88e1fa88d390226e6c7d07340fb4ae54","tarball":"https://registry.npmjs.org/@arms/rum-browser-nextjs/-/rum-browser-nextjs-0.1.0.tgz","fileCount":44,"integrity":"sha512-xTbwsJq4S7jMDxOwZiQI8ZBLnIQU9uvpoNthzsi+A2zvW3BI8M2vTUwEpwXumj6eZgmtNlq/dc5HW5Cx8cD76w==","signatures":[{"sig":"MEQCICCXqZCVbCbgzFA7gCPpfU0gS9OY/gpf+BcInM9TMhHYAiAYiP11RSPW+yyH97YUVJIBUTsOIygHDOtQsOdnIfuDgA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":79292},"main":"lib/index.js","types":"es/index.d.ts","module":"es/index.js","exports":{".":{"types":"./es/index.d.ts","import":"./es/index.js","default":"./es/index.js","require":"./lib/index.js"},"./app-router":{"types":"./es/entries/app-router.d.ts","import":"./es/entries/app-router.js","default":"./es/entries/app-router.js","require":"./lib/entries/app-router.js"},"./package.json":"./package.json","./pages-router":{"types":"./es/entries/pages-router.d.ts","import":"./es/entries/pages-router.js","default":"./es/entries/pages-router.js","require":"./lib/entries/pages-router.js"}},"gitHead":"dadb0de38e5988b1553a1d75fbb67679130bb9d2","scripts":{"test":"jest --config jest.config.js --no-watchman","build":"build-scripts build --skip-demo","test:react19":"jest --config jest.react19.config.js --no-watchman","prepublishOnly":"npm run build"},"_npmUser":{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},"_npmVersion":"10.9.8","description":"Next.js plugin for @arms/rum-browser SDK (App Router / Pages Router view tracking + Next.js error capture with digest)","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","browserslist":["last 2 Chrome versions","last 2 Safari versions","last 2 Firefox versions","last 2 Edge versions","iOS >= 14","Android >= 8"],"dependencies":{"@arms/rum-browser-react":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.3.0","react":"^18.3.1","react-dom":"^18.3.1","typescript":"^4.9.4","@types/react":"^18","@types/react-dom":"^18"},"peerDependencies":{"next":">=13.0.0","react":">=18","@arms/rum-core":">=0.1.11","@arms/rum-browser":">=0.1.16"},"peerDependenciesMeta":{"next":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/rum-browser-nextjs_0.1.0_1788244713791_0.3461625356598135","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@arms/rum-browser-nextjs","version":"0.1.1","description":"Next.js plugin for @arms/rum-browser SDK (App Router / Pages Router view tracking + Next.js error capture with digest)","author":{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},"license":"ISC","main":"lib/index.js","module":"es/index.js","types":"es/index.d.ts","bin":{"rum-nextjs-init":"bin/rum-nextjs-init.mjs"},"exports":{".":{"types":"./es/index.d.ts","import":"./es/index.js","require":"./lib/index.js","default":"./es/index.js"},"./app-router":{"types":"./es/entries/app-router.d.ts","import":"./es/entries/app-router.js","require":"./lib/entries/app-router.js","default":"./es/entries/app-router.js"},"./pages-router":{"types":"./es/entries/pages-router.d.ts","import":"./es/entries/pages-router.js","require":"./lib/entries/pages-router.js","default":"./es/entries/pages-router.js"},"./package.json":"./package.json"},"keywords":["rum","real-user-monitoring","arms","nextjs","next","react","app-router","pages-router","error-tracking","monitoring"],"sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"build":"build-scripts build --skip-demo","prepublishOnly":"npm run build","test":"jest --config jest.config.js --no-watchman","test:react19":"jest --config jest.react19.config.js --no-watchman"},"dependencies":{"@arms/rum-browser-react":"^0.1.1"},"peerDependencies":{"@arms/rum-core":">=0.1.11","@arms/rum-browser":">=0.1.16","react":">=18","next":">=13.0.0"},"peerDependenciesMeta":{"next":{"optional":true},"react":{"optional":true}},"devDependencies":{"typescript":"^4.9.4","@types/react":"^18","@types/react-dom":"^18","react":"^18.3.1","react-dom":"^18.3.1","next":"^16.3.0"},"browserslist":["last 2 Chrome versions","last 2 Safari versions","last 2 Firefox versions","last 2 Edge versions","iOS >= 14","Android >= 8"],"_id":"@arms/rum-browser-nextjs@0.1.1","gitHead":"61aace64cebd4f547e15ad213f74bca3ca7492b7","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-Gx7JdX8X0tCkWFbrrwc2a+zQEVzKbwUrq22kPRWs8EL1Oy86iuTuuVwAebmlc+Y6xffBPY970BcDQWRotZNwXQ==","shasum":"c4eb9b7de578297f316e2887a77a7fe6e651164a","tarball":"https://registry.npmjs.org/@arms/rum-browser-nextjs/-/rum-browser-nextjs-0.1.1.tgz","fileCount":44,"unpackedSize":78515,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCN9DaPbklINoxuQbFRBapZ+7Z7lo4RaBim4VwSSb+FeAIhAL9S60Eh6EvMTjVyoBJo+Bne+F56t6mqq/4PYDp7m2dU"}]},"_npmUser":{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},"directories":{},"maintainers":[{"name":"fengsir","email":"fengsir@live.com"},{"name":"yunjin4","email":"xiashujin.xsj@alibaba-inc.com"},{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},{"name":"yangling1996","email":"yl260087@alibaba-inc.com"},{"name":"yunyi-alibaba","email":"ly403664@alibaba-inc.com"},{"name":"alibaba_rum_harmony","email":"yanglanxin.ylx@alibaba-inc.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rum-browser-nextjs_0.1.1_1788268074673_0.4264170408195018"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-01T06:38:33.622Z","modified":"2026-09-01T13:07:55.033Z","0.1.0":"2026-09-01T06:38:33.946Z","0.1.1":"2026-09-01T13:07:54.817Z"},"author":{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},"license":"ISC","keywords":["rum","real-user-monitoring","arms","nextjs","next","react","app-router","pages-router","error-tracking","monitoring"],"description":"Next.js plugin for @arms/rum-browser SDK (App Router / Pages Router view tracking + Next.js error capture with digest)","maintainers":[{"name":"fengsir","email":"fengsir@live.com"},{"name":"yunjin4","email":"xiashujin.xsj@alibaba-inc.com"},{"name":"guangli.fj","email":"guangli.fj@alibaba-inc.com"},{"name":"yangling1996","email":"yl260087@alibaba-inc.com"},{"name":"yunyi-alibaba","email":"ly403664@alibaba-inc.com"},{"name":"alibaba_rum_harmony","email":"yanglanxin.ylx@alibaba-inc.com"}],"readme":"# @arms/rum-browser-nextjs\n\nNext.js 插件 for `@arms/rum-browser` RUM SDK —— 同时支持 **App Router（Next.js 13+）与 Pages Router**。\n\n## Overview\n\n本包为 Next.js 应用提供框架级可观测适配：自动检测 App Router 与 Pages Router 路由变化、将动态路由收敛为模板化 view 名称、采集 Next.js 错误边界异常并携带 `digest` 关联键。结合 `@arms/rum-browser` 基座能力，可追踪 Core Web Vitals、资源加载、用户操作与会话回放，定位性能瓶颈与用户旅程中的问题。\n\n能力范围：\n\n- **路由视图追踪（view.name 模板收敛）**：开启手动 view 模式后，view 的创建、命名与计时完全由路由变化驱动（内部调用 `shell.startView`）。view.name 自动收敛为路由模板 —— App Router 经「具体 pathname + 参数」反推（`/user/42` → `/user/[id]`，catch-all `/docs/a/b/c` → `/docs/[...slug]`）；Pages Router 直接采用 `router.pathname`（本身就是模板）\n- **Next.js 错误采集**：`addNextjsError` 面向 `app/error.tsx` / `app/global-error.tsx` 接收的错误参数上报，`snapshots` 自动携带 `digest`（生产模式下关联服务端日志的唯一键）与 `framework: 'nextjs'` 标识；附 `ErrorBoundary` 组件\n- **纯客户端集成档**：本包为轻量客户端适配层，不含服务端监控、next.config 包装、sourcemap 上传（见「已知限制」与演进说明）\n\n## 安装\n\n本插件不独立工作 —— 需与主包 `@arms/rum-browser`（必需 peer dependency）同时安装，并先完成主包初始化（见「App Router 接入」）：\n\n```bash\nnpm install @arms/rum-browser @arms/rum-browser-nextjs\n```\n\n`@arms/rum-browser-react` 是本包的内部依赖（非 peer），npm 会自动安装，用户无需单独安装或关心。\n\n## 版本要求\n\n| 依赖                | 版本要求   | 必选 | 说明                                                                                                                   |\n| ------------------- | ---------- | ---- | ---------------------------------------------------------------------------------------------------------------------- |\n| `@arms/rum-core`    | `>=0.1.11` | 是   | 含 `Shell.startView` 与 `IConfiguration.trackViewsManually` 的发版（已发布）                                           |\n| `@arms/rum-browser` | `>=0.1.16` | 是   | 含 PvCollector 手动模式防护（`trackViewsManually` 早退）的发版（已发布）                                               |\n| `react`             | `>=18`     | 否   | optional：组件本质是 React 组件，但 peer 标记为 optional 以兼容边缘消费场景（npm 7+ 自动解析传递 peer）                |\n| `next`              | `>=13.0.0` | 否   | optional：仅 `./app-router`（`next/navigation`）与 `./pages-router`（`next/router`）两个子入口使用；主入口零 next 依赖 |\n\n## 选择你的 Router\n\n> **入口决策**：App Router（项目含 `app/` 目录）→ `@arms/rum-browser-nextjs/app-router`；Pages Router（项目含 `pages/` 目录）→ `@arms/rum-browser-nextjs/pages-router`；两者并存时 App Router 优先。不确定项目用的是哪种 Router？看项目根目录：有 `app/` 即为 App Router，有 `pages/` 且无 `app/` 即为 Pages Router。\n\n## 快速开始（5 分钟最短接入）\n\n适用 Next.js 15.3+ App Router 项目的最短路径（三步）。每步仅列必填项，详细字段见下方「App Router 接入」。\n\n> **一键生成（可选）**：在项目根目录执行 `npx -p @arms/rum-browser-nextjs rum-nextjs-init`，自动探测 Router 类型（App/Pages、`src/` 布局）与 Next 版本（15.3+ / 13~14 降级），生成 `instrumentation-client.ts`、`app/error.tsx` 并输出 `layout.tsx` / `_app.tsx` 待合并片段；已存在的文件默认跳过（`--dry-run` 预览、`--force` 覆盖）。以下三步为手工接入的等价说明。\n\n**1. 安装（2 个包）**\n\n```bash\nnpm install @arms/rum-browser @arms/rum-browser-nextjs\n```\n\n**2. 初始化并重导出路由钩子 —— `instrumentation-client.ts`（项目根）**\n\n```ts\nimport armsRum from '@arms/rum-browser';\nimport { initNextjsPlugin } from '@arms/rum-browser-nextjs';\n\narmsRum.init({ endpoint: 'https://<你的 RUM 上报端点>' });\ninitNextjsPlugin(armsRum);\nexport { onRouterTransitionStart } from '@arms/rum-browser-nextjs';\n```\n\n**3. 根布局挂载检测组件 + error.tsx 上报 —— `app/layout.tsx`、`app/error.tsx`**\n\n```tsx\n// app/layout.tsx\nimport { ArmsAppRouter } from '@arms/rum-browser-nextjs/app-router';\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html>\n      <body>\n        <ArmsAppRouter />\n        {children}\n      </body>\n    </html>\n  );\n}\n```\n\n```tsx\n// app/error.tsx\n'use client';\nimport { useEffect } from 'react';\nimport { addNextjsError } from '@arms/rum-browser-nextjs';\nexport default function Error({\n  error,\n}: {\n  error: Error & { digest?: string };\n}) {\n  useEffect(() => {\n    addNextjsError(error);\n  }, [error]);\n  return <button onClick={() => location.reload()}>重试</button>;\n}\n```\n\n> 完成后启动 `next dev`，访问任一路由即可在平台看到首条 `view` 事件。Pages Router 或 Next.js 13/14 项目见下文对应章节。\n\n## App Router 接入（推荐路径，Next.js 15.3+）\n\n### 1. 初始化插件并重导出路由过渡钩子 —— `instrumentation-client.ts`\n\n在项目根（或 `src/`）目录新建 `instrumentation-client.ts`（Next.js 15.3+ 的客户端监测入口，hydration 前执行，不阻塞首屏渲染）：\n\n```ts\n// instrumentation-client.ts\nimport armsRum from '@arms/rum-browser';\nimport { initNextjsPlugin } from '@arms/rum-browser-nextjs';\n\narmsRum.init({\n  endpoint: 'https://<你的 RUM 上报端点>', // 必填\n  sessionConfig: {},\n  collectors: {\n    pv: { enable: true },\n    jsError: { enable: true },\n    // ... 其余采集器按需开启（与 @arms/rum-browser 配置一致）\n  },\n});\n\n// 插件接入不再依赖 init 完成，可在 init 前后任意时序同步调用\ninitNextjsPlugin(armsRum);\n\n// 如需自定义路由集成（方式 a，可选）：\n// import { createRouterConfig } from '@arms/rum-browser-react/react-router-v6';\n// initNextjsPlugin(armsRum, { router: createRouterConfig({ routes }) });\n\n// 重导出 Next.js 路由过渡钩子（必须）：\n// App Router 导航时 React 渲染先于 pushState 执行，渲染期读取 window.location\n// 拿到的是上一个路由的旧值（url 滞后一拍）。Next.js 在路由过渡开始（渲染前）\n// 回调该钩子，插件捕获导航目标 URL 并在下一次 view 上报中一次性消费 ——\n// 不重导出则 view 事件的 url 将滞后一次导航。\nexport { onRouterTransitionStart } from '@arms/rum-browser-nextjs';\n```\n\n### 2. 根布局挂载 view 检测组件 —— `app/layout.tsx`\n\n```tsx\n// app/layout.tsx\nimport { ArmsAppRouter } from '@arms/rum-browser-nextjs/app-router';\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html lang=\"zh-CN\">\n      <body>\n        <ArmsAppRouter />\n        {children}\n      </body>\n    </html>\n  );\n}\n```\n\n`ArmsAppRouter` 在渲染期通过 `usePathname` + `useParams` 检测路由变化，反推路由模板后调用 `startView`，组件返回 `null`（无渲染输出、无 hydration mismatch）。\n\n### 3. 错误组件上报 —— `app/error.tsx` / `app/global-error.tsx`\n\n```tsx\n// app/error.tsx（app/global-error.tsx 同理）\n'use client';\nimport { useEffect } from 'react';\nimport { addNextjsError } from '@arms/rum-browser-nextjs';\n\nexport default function Error({\n  error,\n  reset,\n}: {\n  error: Error & { digest?: string };\n  reset: () => void;\n}) {\n  useEffect(() => {\n    // 生产模式下 Server Component 抛出的错误 message 会被 generic 化\n    // （如 \"An error occurred in the Server Components render\"），\n    // digest 是关联服务端日志的唯一稳定键 —— addNextjsError 自动携带\n    addNextjsError(error);\n  }, [error]);\n\n  return (\n    <div>\n      <h2>页面出错了</h2>\n      <button onClick={() => reset()}>重试</button>\n    </div>\n  );\n}\n```\n\n> 验证提醒：digest 仅在**生产构建**（`next build && next start`）下是可预期的错误关联形态（dev 下 message 保留原文、digest 可能缺失），联调验证请以 prod 模式为准。\n\n## Pages Router 接入\n\n在 `pages/_app.tsx` 挂载 `ArmsPagesRouter`；初始化方式与 App Router 相同（Next.js 15.3+ 推荐仍经 `instrumentation-client.ts`，13/14 移入客户端组件 `useEffect`）：\n\n```tsx\n// pages/_app.tsx\nimport { useEffect } from 'react';\nimport type { AppProps } from 'next/app';\nimport armsRum from '@arms/rum-browser';\nimport { initNextjsPlugin } from '@arms/rum-browser-nextjs';\nimport { ArmsPagesRouter } from '@arms/rum-browser-nextjs/pages-router';\n\nexport default function App({ Component, pageProps }: AppProps) {\n  useEffect(() => {\n    armsRum.init({\n      endpoint: 'https://<你的 RUM 上报端点>', // 必填\n    });\n    initNextjsPlugin(armsRum);\n  }, []);\n\n  return (\n    <>\n      <ArmsPagesRouter />\n      <Component {...pageProps} />\n    </>\n  );\n}\n```\n\nPages Router 下 `onRouterTransitionStart` 重导出非必需：`ArmsPagesRouter` 检测到导航时以 `router.asPath` 作为显式 URL 传入（优先级高于预捕获值），url 无滞后问题。\n\n> **hydration 前提**：`ArmsPagesRouter` 为客户端组件，view 检测以 React hydration（客户端接管）启动为前提 —— hydration 启动后渲染期路由比较才会执行。个别环境（部分 Electron / WebView 壳浏览器）不启动 Pages Router 的 hydration，此时 `ArmsPagesRouter` 不渲染、view 不会上报 —— 这是环境限制而非插件缺陷（App Router 不受影响）。接入前如需确认目标环境行为，可先在页面组件的 `useEffect` 中设置标记（如 `window.__HYDRATED__ = true`）验证 hydration 是否启动。\n\n## Next.js 13 / 14 接入（退化指引）\n\nNext.js 13/14 无 `instrumentation-client.ts` 入口，初始化移入任意客户端组件的 `useEffect`：\n\n```tsx\n// app/rum-init.tsx（在根 layout 中挂载：<RumInit />）\n'use client';\nimport { useEffect } from 'react';\nimport armsRum from '@arms/rum-browser';\nimport { initNextjsPlugin } from '@arms/rum-browser-nextjs';\n\nexport function RumInit() {\n  useEffect(() => {\n    armsRum.init({\n      endpoint: 'https://<你的 RUM 上报端点>', // 必填\n    });\n    initNextjsPlugin(armsRum);\n  }, []);\n  return null;\n}\n```\n\n行为差异：\n\n- **view.name 收敛不受影响** —— 渲染期路由比较机制全版本可用；\n- **仅丢失 url 精确捕获**：无路由过渡钩子可重导出，路由切换时 view 的 `url` 退化为渲染期的 `window.location.href`（可能滞后一次导航）；\n- 插件以行为信号检测该场景（路由切换未捕获到预捕获 URL，无论成因是 Next < 15.3 还是 15.3+ 忘记 re-export），打印一次性的 `logger.warn` 提示 —— 属预期降级提示，非错误。\n\n## 路由追踪说明\n\n> **警告：接入本插件后 view/PV 完全由路由组件驱动，主包自动 PV 被禁用**\n>\n> 插件（经转发的 `initReactPlugin`）注入 `trackViewsManually: true`：主包 PvCollector 跳过首屏 PV 与 history 拦截（避免双发），view 的创建、命名、计时完全依赖 `ArmsAppRouter` / `ArmsPagesRouter` 在渲染期检测路由变化驱动（内部调用 `shell.startView`）。这意味着 view 上报以路由检测组件真实挂载为前提 —— 未挂载 `ArmsAppRouter` / `ArmsPagesRouter` 时 view 将不会上报（无报错、无告警）。\n\nview 上报行为：\n\n- **view.name 收敛**：App Router 经 `computeViewNameFromParams` 由「具体 pathname + 参数」反推模板（算法见下文 API 参考）；Pages Router 直接采用 `router.pathname`（本身就是路由模板）\n- **url 优先级**：Pages Router 显式 `asPath` > `onRouterTransitionStart` 预捕获值（一次性消费，防串用）> 渲染期 `window.location.href` 兜底；最终统一绝对化后上报\n- **loading_type**：首个 view 为 `initial_load`，此后为 `route_change`\n- 同名动态路由的不同具体 URL（`/user/42` → `/user/43`）经 pathname/asPath 变化检测正常触发上报调用，view 是否新建遵循 `Shell.startView` 同名去重语义（模板相同不新建，与 react-router 场景一致）\n\n```ts\n// 访问 /user/42 时上报的 view 事件：\n// { event_type: 'view', type: 'pv', name: '/user/[id]', loading_type: 'initial_load', url: 'https://...', ... }\n```\n\n## 错误采集说明\n\n`addNextjsError(error, errorInfo?)` 上报的事件结构与 `@arms/rum-browser-react` 的 `addReactError` 对齐（`source: 'react'`，复用既有存储/查询链路），差异仅在 `snapshots` 内附加 `digest` 与 `framework: 'nextjs'`：\n\n```ts\n{\n  event_type: 'exception',\n  type: 'error',\n  source: 'react',\n  name: 'Error',                  // error.name（非 Error 值自动 normalize）\n  message: \"An error occurred in the Server Components render...\",\n  stack: '...',\n  // 扩展信息收敛为单一 JSON 字符串字段（值为 undefined 的内部键不会出现）\n  snapshots: JSON.stringify({\n    component_stack: 'at UserProfile\\nat ...',  // errorInfo.componentStack（传入时）\n    handling: 'handled',\n    digest: '3669057531',                       // error.digest（存在时）\n    framework: 'nextjs',\n  }),\n  times: 1,\n}\n```\n\n`ErrorBoundary` 组件包裹组件树时自动走 `addNextjsError` 上报（用法与 `@arms/rum-browser-react` 的 `ErrorBoundary` 一致，`fallback` 接收 `error` 与 `resetError`）。\n\n### 错误聚合注意事项\n\ncore 的 reporter 对 exception 类事件按 `getErrorID(message + stack)` 聚合，聚合 key **不包含 `source` 字段**。当同一错误同时被全局异常采集通道（`window.onError`）与 `addNextjsError` 捕获时，`addNextjsError` 上报的 `source='react'` 富上下文事件（含 `digest` / `framework: 'nextjs'`）可能被先到的全局事件通过 `times++` 合并吞掉，无法独立可见。典型场景是 `app/global-error.tsx`：错误在客户端被重放时会先触发 `window.onError` 入队。\n\n**规避方法**：通过 init 配置的 `filters.exception` 过滤对应错误 —— filters 仅作用于 collector 自动采集通道，不影响 `addNextjsError`（`sendEvent` 直发）上报；过滤全局通道后 `source='react'` 事件即可独立上报（times=1）。参考示例：`examples/browser-nextjs/instrumentation-client.ts` 中有实际用法。\n\n## API 参考\n\n### 主入口 `@arms/rum-browser-nextjs`\n\n| 导出                                                                       | 说明                                                                                                                                                           |\n| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `initNextjsPlugin(shell: Shell, options?: NextjsPluginOptions): void`      | 初始化插件：缓存 shell + 转发 `initReactPlugin`（继承 `trackViewsManually` 注入/首屏防御/幂等守卫）。`options.router` 透传给 `initReactPlugin`（方式 a，可选） |\n| `onRouterTransitionStart(url: string): void`                               | 路由过渡钩子 —— 须在 `instrumentation-client.ts` 中 re-export；捕获导航目标 URL，解决渲染期 url 滞后一拍                                                       |\n| `addNextjsError(error: unknown, errorInfo?: ErrorInfo): void`              | 手动上报 Next.js 渲染异常（`app/error.tsx` / `global-error.tsx` 接收的 error 参数）；`snapshots` 附 `digest` 与 `framework: 'nextjs'`                          |\n| `ErrorBoundary: ComponentType<ErrorBoundaryProps>`                         | Next.js 场景错误边界（`createErrorBoundary(addNextjsError)` 工厂组装），上报自动携带 digest                                                                    |\n| `computeViewNameFromParams(pathname: string, params: RouteParams): string` | 路由模板反推工具（高级用法/测试可用）                                                                                                                          |\n\n类型导出：`NextjsError`（`Error & { digest?: string }`）、`RouteParams`（`Record<string, string | string[] | undefined>`）、`ErrorBoundaryProps` / `ErrorBoundaryFallback`（自 `@arms/rum-browser-react` type-only re-export）。\n\n刻意不导出 `useRum` 等通用 React 能力 —— 需要者直接从 `@arms/rum-browser-react` 导入（最小 API 暴露原则）。\n\n### 子入口 `@arms/rum-browser-nextjs/app-router`\n\n| 导出                    | 说明                                                              |\n| ----------------------- | ----------------------------------------------------------------- |\n| `ArmsAppRouter(): null` | App Router view 检测组件，挂载于根 layout；返回 `null` 无渲染输出 |\n\n### 子入口 `@arms/rum-browser-nextjs/pages-router`\n\n| 导出                      | 说明                                                                            |\n| ------------------------- | ------------------------------------------------------------------------------- |\n| `ArmsPagesRouter(): null` | Pages Router view 检测组件，挂载于 `pages/_app.tsx`；内置 `router.isReady` 守卫 |\n\n### `computeViewNameFromParams(pathname, params)`\n\n两阶段反推算法（App Router 无客户端路由配置，只能由实际 pathname + 参数反推）：\n\n1. **catch-all（数组）参数优先**：`[...slug]` 展开的连续段整体定位并替换回 `[...slug]`（防止值重叠时被普通参数替换吞掉）；\n2. **普通（字符串）参数贪婪左到右**：参数值在段中首次出现的位置替换为 `[paramName]`；多参数同值时按迭代顺序从左到右命名。\n\n**编码对称处理**：`usePathname()` 与 `useParams()` 的编码形态并无稳定契约 —— Next 文档宣称 params 为 decoded，实测 next 15.5.23 两者均返回 URL 编码形态（如 `/user/%E5%BC%A0%E4%B8%89` 与 `{ id: '%E5%BC%A0%E4%B8%89' }`），且历史版本间行为亦有摇摆 —— 直接以原段匹配参数值在「path 与 params 形态不一致」的场景下必然失配，中文等非 ASCII 动态段会退化为具体路径、模板不收敛。本包以「统一解码域匹配」覆盖全部形态组合（匹配双方均经 `decodeURIComponent` 归一，替换同步写入原段）：中文动态段在任一形态组合下正常收敛，返回值中静态段保留原始编码形态（与 Next 自身 pathname 形态一致）。\n\n| `pathname`                 | `params`                         | 返回值                                  |\n| -------------------------- | -------------------------------- | --------------------------------------- |\n| `/blog/hello-world`        | `{}`                             | `/blog/hello-world`（原样）             |\n| `/user/42`                 | `{ id: '42' }`                   | `/user/[id]`                            |\n| `/user/%E5%BC%A0%E4%B8%89` | `{ id: '张三' }`                 | `/user/[id]`（编码对称）                |\n| `/docs/a/b/c`              | `{ slug: ['a', 'b', 'c'] }`      | `/docs/[...slug]`                       |\n| `/shop/item/1/review/1`    | `{ itemId: '1', reviewId: '1' }` | `/shop/item/[itemId]/review/[reviewId]` |\n| `/user/42/posts/42`        | `{ id: '42' }`                   | `/user/[id]/posts/42`（仅首次出现替换） |\n\n> 反推是启发式算法，极端值重叠场景存在理论误差，实际业务影响可忽略。\n\n## 版本兼容矩阵\n\n| Next.js 版本 | 支持情况    | 说明                                                                                                        |\n| ------------ | ----------- | ----------------------------------------------------------------------------------------------------------- |\n| 15.3+        | ✅ 完整能力 | `instrumentation-client.ts` 初始化 + `onRouterTransitionStart` URL 预捕获（无滞后）                         |\n| 13 ~ 14      | ⚠️ 退化支持 | 初始化移入客户端组件 `useEffect`（见「Next.js 13 / 14 接入」）；view.name 收敛不受影响，仅丢失 url 精确捕获 |\n| Turbopack    | ✅ 支持     | exports 全条件带 `.js` 扩展名（Next 16 起 Turbopack 默认打包器）                                            |\n| webpack 5    | ✅ 支持     | Next.js 内置构建链                                                                                          |\n\n> 本包仅经 bundler（npm 工程）消费，不出 UMD dist 产物；目标环境为 Next.js 工程（内置 webpack 5 / Turbopack），不涉及 webpack 4（未提供根级存根，区别于 `@arms/rum-browser-react` 的 v4 兼容存根）。\n\n### React 兼容性\n\n| React 版本 | 支持情况  | 说明                                                                                                              |\n| ---------- | --------- | ----------------------------------------------------------------------------------------------------------------- |\n| 18.x       | ✅ 已验证 | devDeps 锁定 `^18.3.1`，单测与 E2E 全量通过                                                                       |\n| 19.x       | ✅ 已验证 | peer 放开为 `>=18`；本地以 React 19.0.0 一次性运行全量单测（38/38 通过），view/error/ErrorBoundary 行为与 18 一致 |\n\n> **产物压缩说明**：`es` / `lib` 产物已经过 terser 压缩与混淆，且不附带 sourcemap。排查线上堆栈时请对照仓库源码与 [CHANGELOG](./CHANGELOG.md) 版本定位对应发布版本。\n\n## 已知限制\n\n> 以下限制是当前设计所固有的，后续版本计划改进。\n\n### query-only 导航不触发新 view\n\n`/list?page=1` → `/list?page=2` 仅 query 变化：App Router 的 `usePathname` 与 Pages Router 的 `asPath` path 段检测均不视为导航 —— 不产生新 view（预期行为）。query 维度变化如需观测请通过自定义事件补齐。\n\n### parallel / interception routes 不触发新 view\n\n并行路由与拦截路由不改变 primary pathname，渲染期比较无法感知（预期行为）。Next.js 16.3+ 的 `fromRoutes`（experimental）是官方给出的未来解法，列入演进路径。\n\n### CDN 场景指引\n\n本包仅经 bundler 消费（无 UMD 产物），CDN（`<script>` 标签）接入场景不适用本插件 —— 请直接使用主包 `@arms/rum-browser`（自动 PV + 全局异常采集）。若以 CDN 方式加载主包，请在 App Router 下用 `next/script` 的 `afterInteractive` 策略加载；**勿用 `beforeInteractive`**（App Router 下不支持且阻塞首屏渲染）。\n\n### 单实例约束\n\n插件为模块级单例（导航状态持有 shell 引用，与 `@arms/rum-browser-react` 一致）。多 RUM 实例场景下最后一次初始化生效；微前端多子应用各自持有独立实例的场景暂不支持，列入后续演进。\n\n### `source='react'` 错误不受 `collectors.jsError` 采样管控\n\n`addNextjsError` / `ErrorBoundary` 上报的错误携带 `source: 'react'`。browser SDK 的 `SessionProcessor` 采样键映射当前未包含 `'react'` source，这些错误会绕过 `collectors.jsError` 采样配置。如需禁用，请使用 `collectors.exception`（设置为 `false`）。后续版本可能会新增 `'react'` → `'js'` 的 source 映射。\n\n### 初始化时序\n\n`initNextjsPlugin` 可在 `armsRum.init()` 前后任意时序同步调用 —— `useCollectors` 的双时序注册机制保证 collector 在 init 完成后统一 setup。初始化完成之前路由组件或 `addNextjsError` 触发的上报会安全降级（打印告警并跳过，不抛错）。\n\n推荐写法是同步调用（`initNextjsPlugin(armsRum)` 紧跟 `armsRum.init({...})` 之后），这也是本包示例工程的写法。若使用 `remote-config` 远端优先模式（init 晚于 hydration），早到的 view 会因 client 未就绪被丢弃并告警 —— 此场景下可改用 `armsRum.init({...}).then(() => initNextjsPlugin(armsRum))` 确保首屏 view 就绪率。\n","readmeFilename":"README.md"}