ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

AG Kit Next.js 性能专家规则详解:服务端性能 7 条规则(Server-Side Performance)

AG Kit Next.js 性能专家规则详解:服务端性能 7 条规则(Server-Side Performance) AG Kit Next.js 性能专家规则详解服务端性能 7 条规则Server-Side Performance【免费下载链接】ag-kit项目地址: https://gitcode.com/GitHub_Trending/an/ag-kit本篇基于 ag-kit 仓库中的 Next.js/React 性能专家技能文档3-server-server-side-performance.md展开完整解读其中 7 条服务端性能规则Rule 3.1–3.7Server Action 鉴权、RSC 边界序列化瘦身、并行数据获取、跨请求 LRU 缓存、React.cache()请求内去重与after()非阻塞操作。读完你可以掌握一套可直接落地到 Next.js 16 / React 19 项目的服务端性能与安全优化方案并能理解 ag-kit 自身站点代码是如何体现这些原则的。一、规则集定位ag-kit 性能专家体系中的 HIGH 级章节ag-kit 是一个面向 AI Agent 的工具箱仓库其.agents/skills/nextjs-react-expert/目录下内置了一套来自 Vercel Engineering 的 58 条 React/Next.js 性能优化规则按影响级别分为 9 个章节组织入口见 SKILL.md。本文聚焦的第 3 章 Server-Side Performance 在该体系中的定位是属性取值Impact章节级HIGH规则数量7 条3.1–3.7核心目标优化服务端渲染与数据获取消除服务端瀑布waterfall缩短响应时间适用场景Slow SSR、API route 优化、服务端数据瀑布排查SKILL.md 的 Impact Priority Guide 将优化顺序定义为先做 CRITICAL消除瀑布、瘦身 bundle再做 HIGH服务端性能最后做 MEDIUM/LOW。也就是说当页面级瀑布和 bundle 问题已基本解决后服务端就是下一个收益来源。与当前仓库的契合度ag-kit 自带的文档站点web/使用的技术栈与该技能声明的“Next.js 16 / React 19 era”完全一致——web/package.json 中声明了next: ^16.2.11与react: 19.2.3。因此本文的 7 条规则可以直接对 ag-kit 自身站点代码做对照分析而非纸面理论。二、Rule 3.1像对待 API 路由一样鉴权 Server ActionImpact: CRITICALTags:server, server-actions, authentication, security, authorization这是 7 条规则中唯一标记为 CRITICAL 的一条属于安全与性能交叉点Server Actions带use server的函数会作为公共端点暴露与 API 路由无异。必须把认证与授权校验放在每个 Server Action 内部——不要只依赖 middleware、layout 守卫或页面级检查因为 Server Action 可以被直接调用。Next.js 官方文档的原文表述也被规则收录“Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.”以对待公共 API 端点的同等安全标准对待 Server Action并验证用户是否被允许执行该变更。反例无鉴权use server export async function deleteUser(userId: string) { // Anyone can call this! No auth check await db.user.delete({ where: { id: userId } }) return { success: true } }任何拿到端点的人都可触发deleteUser直接造成越权数据删除。正例在 Action 内部完成认证 授权use server import { verifySession } from /lib/auth import { unauthorized } from /lib/errors export async function deleteUser(userId: string) { // Always check auth inside the action const session await verifySession() if (!session) { throw unauthorized(Must be logged in) } // Check authorization too if (session.user.role ! admin session.user.id ! userId) { throw unauthorized(Cannot delete other users) } await db.user.delete({ where: { id: userId } }) return { success: true } }注意这里的两层校验认证verifySession确认“你是谁”与授权角色 资源属主检查确认“你能不能”。进阶先做输入校验use server import { verifySession } from /lib/auth import { z } from zod const updateProfileSchema z.object({ userId: z.string().uuid(), name: z.string().min(1).max(100), email: z.string().email() }) export async function updateProfile(data: unknown) { // Validate input first const validated updateProfileSchema.parse(data) // Then authenticate const session await verifySession() if (!session) { throw new Error(Unauthorized) } // Then authorize if (session.user.id ! validated.userId) { throw new Error(Can only update own profile) } // Finally perform the mutation await db.user.update({ where: { id: validated.userId }, data: { name: validated.name, email: validated.email } }) return { success: true } }推荐顺序是Validate → Authenticate → Authorize → Mutate先用 Zod 拒绝畸形输入再做会话与权限检查最后才执行数据库变更避免无效输入消耗数据库连接与鉴权开销。三、Rule 3.2避免 RSC Props 的重复序列化Impact: LOWTags:server, rsc, serialization, props, client-componentsRSC 到客户端的序列化按对象引用去重而不是按值去重同一引用只序列化一次新引用则再序列化一次。因此不要在服务端做.toSorted()、.filter()、.map()这类变换后再传给客户端组件——变换会产生新引用导致同一份数据被重复发送。反例同一数组传两份// RSC: sends 6 strings (2 arrays × 3 items) ClientList usernames{usernames} usernamesOrdered{usernames.toSorted()} /正例只传一份客户端自行变换// RSC: send once ClientList usernames{usernames} / // Client: transform there use client const sorted useMemo(() [...usernames].sort(), [usernames])嵌套去重行为影响程度因数据类型而异去重是递归生效的但收益大小取决于数据结构string[]、number[]、boolean[]HIGH impact——数组容器 全部原始值都会被完整复制object[]LOW impact——只有数组结构被复制嵌套对象本身仍按引用去重。// string[] - duplicates everything usernames{[a,b]} sorted{usernames.toSorted()} // sends 4 strings // object[] - duplicates array structure only users{[{id:1},{id:2}]} sorted{users.toSorted()} // sends 2 arrays 2 unique objects (not 4)会破坏去重的操作创建新引用数组.toSorted()、.filter()、.map()、.slice()、[...arr]对象{...obj}、Object.assign()、structuredClone()、JSON.parse(JSON.stringify())// ❌ Bad C users{users} active{users.filter(u u.active)} / C product{product} productName{product.name} / // ✅ Good C users{users} / C product{product} / // Do filtering/destructuring in client例外当变换代价很高、或客户端根本不需要原始数据时传派生数据是合理的——此时避免重复序列化的目标要让位于服务端计算成本。四、Rule 3.3跨请求 LRU 缓存Impact: HIGHTags:server, cache, lru, cross-requestReact.cache()只在单个请求内生效。而当用户在短时间内顺序点击按钮 A 再点击按钮 B两个连续请求需要同一份数据时需要一个跨请求的 LRU 缓存import { LRUCache } from lru-cache const cache new LRUCachestring, any({ max: 1000, ttl: 5 * 60 * 1000 // 5 minutes }) export async function getUser(id: string) { const cached cache.get(id) if (cached) return cached const user await db.user.findUnique({ where: { id } }) cache.set(id, user) return user } // Request 1: DB query, result cached // Request 2: cache hit, no DB query适用场景用户连续操作在短时间内多次命中需要相同数据的多个端点。部署形态决定缓存策略在 Fluid Compute 这类允许多个并发请求共享同一函数实例的环境中LRU 缓存尤为有效——缓存跨请求驻留无需 Redis 等外部存储在传统 serverless 中每次调用相互隔离跨进程共享需考虑 Redis。选型上可参考node-lru-cache库从规则给出的参数看容量上限max与过期时间ttl是防止内存膨胀的两大关键配置。五、Rule 3.4最小化 RSC 边界处的序列化Impact: HIGHTags:server, rsc, serialization, propsReact Server/Client 边界会把对象的所有属性序列化为字符串并嵌入 HTML 响应及后续 RSC 请求中。这份序列化数据直接计入页面体积与加载时间尺寸很重要只传客户端真正用到的字段。反例50 个字段全部过界async function Page() { const user await fetchUser() // 50 fields return Profile user{user} / } use client function Profile({ user }: { user: User }) { return div{user.name}/div // uses 1 field }客户端只用了name一个字段却为 50 个字段付了序列化成本。正例只传 1 个字段async function Page() { const user await fetchUser() return Profile name{user.name} / } use client function Profile({ name }: { name: string }) { return div{name}/div }ag-kit 站点的真实应用这条规则在 ag-kit 的文档站点中有一个有意思的“反向”实践。以 brainstorm 示例页 为例页面在服务端把四种语言的 MDX 内容各渲染成一个 RSC 元素作为 props 传给客户端组件export default function Page() { return LocalizedDoc en{En /} vi{Vi /} zh{Zh /} ja{Ja /} /; }而 LocalizedDoc 是use client组件源码注释明确写道所有分支都在服务端渲染完成客户端组件“只负责选择显示哪一个”因此切换语言无需刷新页面即可瞬时生效。从源码结构看这是一种有意识的权衡把“四选一”的选择逻辑下放到客户端换取即时切换体验同时利用 RSC 边界去重同一 locale 的内容只作为一份 RSC 元素传递而不是把四份原始 MDX 文本全部序列化到浏览器。这恰好印证了 Rule 3.4 与 3.2 的核心思想——RSC 边界两侧各干各擅长的事边界上的数据尽量少、尽量按引用共享。六、Rule 3.5组件组合实现并行数据获取Impact: CRITICALTags:server, rsc, parallel-fetching, compositionReact Server Components 在组件树内部是顺序执行的Page中先await fetchHeader()完成才会执行子组件Sidebar的取数逻辑。要用组件组合重构让各部分的数据获取并行发生。反例Sidebar 被迫等待 Page 的 fetchexport default async function Page() { const header await fetchHeader() return ( div div{header}/div Sidebar / /div ) } async function Sidebar() { const items await fetchSidebarItems() return nav{items.map(renderItem)}/nav }总耗时 ≈fetchHeader()耗时 fetchSidebarItems()耗时串行瀑布。正例两个组件同时取数async function Header() { const data await fetchHeader() return div{data}/div } async function Sidebar() { const items await fetchSidebarItems() return nav{items.map(renderItem)}/nav } export default function Page() { return ( div Header / Sidebar / /div ) }取数下沉到各自的异步 Server Component 后Header与Sidebar的请求同时发起总耗时 ≈ 两者中的最大值。变体children prop 组合async function Header() { const data await fetchHeader() return div{data}/div } async function Sidebar() { const items await fetchSidebarItems() return nav{items.map(renderItem)}/nav } function Layout({ children }: { children: ReactNode }) { return ( div Header / {children} /div ) } export default function Page() { return ( Layout Sidebar / /Layout ) }Layout本身不 await 任何数据Header与children即Sidebar独立并行。这种“壳组件不取数、取数组件各自独立”的结构也是配合 Suspense 做流式渲染的基础。七、Rule 3.6用 React.cache() 做单请求内去重Impact: MEDIUMTags:server, cache, react-cache, deduplicationReact.cache()用于服务端请求内去重认证检查和数据库查询受益最大import { cache } from react export const getCurrentUser cache(async () { const session await auth() if (!session?.user?.id) return null return await db.user.findUnique({ where: { id: session.user.id } }) })在同一个请求内组件树中多处调用getCurrentUser()只会执行一次查询其余调用直接拿到缓存结果。陷阱内联对象参数导致缓存永远未命中React.cache()用浅比较Object.is判定缓存命中。每次调用都构造新对象引用必然不同缓存必然失效。反例每次调用都是 cache missconst getUser cache(async (params: { uid: number }) { return await db.user.findUnique({ where: { id: params.uid } }) }) // Each call creates new object, never hits cache getUser({ uid: 1 }) getUser({ uid: 1 }) // Cache miss, runs query again正例原始值参数可命中const getUser cache(async (uid: number) { return await db.user.findUnique({ where: { id: uid } }) }) // Primitive args use value equality getUser(1) getUser(1) // Cache hit, returns cached result如果必须传对象请传同一个引用const params { uid: 1 } getUser(params) // Query runs getUser(params) // Cache hit (same reference)Next.js 特有的注意点在 Next.js 中fetchAPI 被自动扩展了请求内 memoization相同 URL 与 options 的请求在单请求内自动去重因此 fetch 调用不需要再套React.cache()。但以下非 fetch 的异步工作仍然必须手动去重数据库查询Prisma、Drizzle 等重量级计算认证检查文件系统操作任何非 fetch 的异步任务八、Rule 3.7用 after() 处理非阻塞操作Impact: MEDIUMTags:server, async, logging, analytics, side-effectsNext.js 的after()用于调度“响应发出之后才需要执行”的工作防止日志、分析等副作用阻塞响应。反例日志阻塞响应import { logUserAction } from /app/utils export async function POST(request: Request) { // Perform mutation await updateDatabase(request) // Logging blocks the response const userAgent request.headers.get(user-agent) || unknown await logUserAction({ userAgent }) return new Response(JSON.stringify({ status: success }), { status: 200, headers: { Content-Type: application/json } }) }正例响应先返回日志后台执行import { after } from next/server import { headers, cookies } from next/headers import { logUserAction } from /app/utils export async function POST(request: Request) { // Perform mutation await updateDatabase(request) // Log after response is sent after(async () { const userAgent (await headers()).get(user-agent) || unknown const sessionCookie (await cookies()).get(session-id)?.value || anonymous logUserAction({ sessionCookie, userAgent }) }) return new Response(JSON.stringify({ status: success }), { status: 200, headers: { Content-Type: application/json } }) }响应立即返回日志在后台完成。典型适用场景包括分析埋点Analytics tracking审计日志Audit logging发送通知缓存失效Cache invalidation清理任务两个重要事实after()在响应失败或重定向时仍会执行它可用于 Server Actions、Route Handlers 和 Server Components。注意正例中headers()/cookies()是await调用的——这是 Next.js 16该仓库web/站点所用的版本的异步 API 形态。九、在 ag-kit 中落地规则导航与自动化审计按症状定位规则SKILL.md 提供了一个决策树遇到 Slow Server-Side Rendering进入 Section 3重点检查 Parallel data fetching 与 streaming做综合性能评审时按 CRITICAL水瀑布、bundle→ HIGH服务端→ MEDIUM客户端取数、重渲染、渲染→ LOWJS 微优化的顺序推进。上文的 7 条规则正好覆盖该章节全部检查面安全3.1、序列化3.2、3.4、缓存3.3、3.6、并行化3.5、非阻塞副作用3.7。自动化审计脚本技能目录附带了 react_performance_checker.py可对任意 Next.js 项目执行静态扫描。其工作方式从源码可见_discover_scan_roots()会优先寻找包含next/react依赖的package.json自动适配类似 ag-kit 这种“根目录 web/子项目”的多包结构依次执行check_waterfalls顺序 await 检测、check_barrel_imports、check_dynamic_imports、check_useEffect_fetching、check_missing_memoization、check_image_optimization等检查项每项发现都会标注对应规则章节如2-bundle-bundle-size-optimization.md便于回溯通过--fail-on-warnings参数可把建议性告警升级为阻断项适合接入 CI。运行方式python .agents/skills/nextjs-react-expert/scripts/react_performance_checker.py project_path需要说明的是该脚本以正则静态扫描为主用于快速发现模式级问题像 Rule 3.1Server Action 是否做了内部鉴权、Rule 3.2props 是否重复序列化这类语义级规则仍应以本文的 7 条规则作为人工评审清单。十、小结规则级别一句话要点3.1 Server Action 鉴权CRITICAL认证/授权必须写在每个 Action 内部按 API 端点标准对待3.2 避免重复序列化LOW序列化按引用去重.toSorted()/.filter()等变换放到客户端做3.3 跨请求 LRU 缓存HIGHReact.cache()仅限单请求顺序请求复用数据用 LRUserverless 用 Redis3.4 最小化 RSC 边界序列化HIGH只传客户端真正用到的字段序列化体积直接计入页面体积3.5 组件组合并行取数CRITICAL取数下沉到独立异步组件把串行瀑布变为并行3.6 React.cache() 单请求去重MEDIUM参数用原始值或稳定引用避免内联对象导致永远 miss3.7 after() 非阻塞操作MEDIUM日志/分析/缓存失效放进after()响应立即返回配合 web/package.json 所示的 Next.js 16.2.x React 19.2.x 环境这 7 条规则构成了一份从安全到吞吐、从单请求到跨请求的完整服务端优化清单先保证 Server Action 安全3.1再通过组件组合消灭服务端瀑布3.5、用两级缓存削减重复查询3.6 请求内 3.3 跨请求最后通过序列化瘦身3.2、3.4和非阻塞副作用3.7把响应时间与页面体积压到最低。【免费下载链接】ag-kit项目地址: https://gitcode.com/GitHub_Trending/an/ag-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表