Next.js App Router服务器组件如何实现服务端错误日志?
Next.js App Router 全局服务端错误处理(日志记录+客户端脱敏)
在Next.js App Router中,要实现服务端完整错误日志记录且不向客户端暴露敏感信息,可通过以下全局方案替代逐个try/catch的繁琐操作:
一、全局捕获Server Component渲染错误
利用根layout.js(Server Component)的包裹特性,捕获所有子Server Component的同步/异步渲染错误,在服务端记录完整日志后返回脱敏内容给客户端。
示例代码:
// app/layout.js import { logErrorToService } from '@/lib/logger'; export default async function RootLayout({ children }) { try { return ( <html lang="zh-CN"> <body>{children}</body> </html> ); } catch (error) { // 服务端记录完整错误信息(含栈追踪、敏感上下文) await logErrorToService({ message: error.message, stack: error.stack, timestamp: new Date().toISOString(), context: 'Server Component Render' }); // 返回脱敏错误页面,不暴露敏感细节 return ( <html lang="zh-CN"> <body className="flex items-center justify-center h-screen"> <div className="text-center"> <h1 className="text-2xl font-bold mb-4">服务端出现异常</h1> <p>我们已收到通知,正在处理,请稍后重试。</p> </div> </body> </html> ); } }
该方案覆盖根路由下所有Server Component的渲染错误,包括并行路由、嵌套路由的组件错误。
二、全局捕获Server Action错误
针对Server Action的未处理错误,有两种全局处理方式:
方式1:Node.js全局错误事件监听
在服务端注册全局错误事件,捕获所有未被try/catch处理的异步/同步错误,适用于Node.js Runtime环境。
示例代码:
// app/global-error-handler.js import { logErrorToService } from '@/lib/logger'; // 捕获未处理的Promise拒绝(异步错误) process.on('unhandledRejection', async (reason, promise) => { await logErrorToService({ message: 'Unhandled Rejection', reason: reason instanceof Error ? reason.message : String(reason), stack: reason instanceof Error ? reason.stack : undefined, promise: promise.toString(), timestamp: new Date().toISOString(), context: 'Server Action' }); }); // 捕获未捕获的同步错误 process.on('uncaughtException', async (error) => { await logErrorToService({ message: 'Uncaught Exception', errorMessage: error.message, stack: error.stack, timestamp: new Date().toISOString(), context: 'Server Action' }); // 同步未捕获错误会导致进程不稳定,捕获后建议优雅退出 process.exit(1); });
在根layout.js中导入该模块,确保服务端启动时加载:
// app/layout.js import './global-error-handler.js'; // ... 其他代码
方式2:自动注入Server Action包装器
通过Babel插件自动给所有标记'use server'的函数加上错误处理包装,无需手动逐个包裹。
- 创建包装函数:
// lib/server-action-wrapper.js import { logErrorToService } from './logger'; export function wrapServerAction(action) { return async (...args) => { try { return await action(...args); } catch (error) { await logErrorToService({ message: 'Server Action Error', actionName: action.name, errorMessage: error.message, stack: error.stack, timestamp: new Date().toISOString(), context: 'Server Action' }); // 抛出脱敏错误给客户端 throw new Error('操作失败,请稍后重试'); } }; }
- 配置Next.js的Babel插件:
// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { babel: { plugins: [ [ './babel-plugins/wrap-server-actions.js', { wrapperPath: '@/lib/server-action-wrapper.js' } ] ] } }; module.exports = nextConfig;
- 自定义Babel插件实现自动包装:
// babel-plugins/wrap-server-actions.js module.exports = function ({ types: t }) { return { visitor: { FunctionDeclaration(path) { const hasUseServer = path.node.directives?.some( dir => dir.value.value === 'use server' ); if (hasUseServer) { // 导入包装函数 const wrapperImport = t.importDeclaration( [t.importSpecifier(t.identifier('wrapServerAction'), t.identifier('wrapServerAction'))], t.stringLiteral('@/lib/server-action-wrapper.js') ); path.hub.file.path.unshiftContainer('body', wrapperImport); // 用包装函数包裹目标Server Action path.replaceWith( t.variableDeclaration('const', [ t.variableDeclarator( path.node.id, t.callExpression(t.identifier('wrapServerAction'), [path.node]) ) ]) ); } } } }; };
三、注意事项
- 所有敏感错误信息仅在服务端日志中记录,客户端仅接收脱敏提示。
- 使用Edge Runtime时,Node.js全局错误事件不生效,需改用对应平台的错误日志服务或在middleware中捕获。
- 根
layout.js的try/catch仅捕获Server Component渲染阶段的错误,若需处理客户端组件错误,仍需配合客户端Error Boundary。
内容的提问来源于stack exchange,提问作者TrueWill
相关产品推荐
相关产品推荐

