关于 performance-audit
performance-audit 是面向开发者的 Web 应用性能审查技能,按阶段检查资源包、数据库、渲染、网络和内存等方面。
主要功能
- 01分阶段审查 Web 性能
- 02分析资源包与依赖
- 03检查数据库查询和索引
- 04审查渲染与重复渲染
- 05检查网络请求与缓存
适用场景
- 01部署前审查性能
- 02调查页面加载缓慢
- 03检查重大功能上线后的性能
- 04评估扩展流量前的应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mgd34msu/ goodvibes-plugin --skill performance-audit安装后在 Claude Code 中输入/performance-audit确认已加载。
附带文件references/performance-patterns.mdscripts/validate-performance-audit.sh
详细介绍
资源
scripts/
validate-performance-audit.sh
references/
performance-patterns.md
性能审计
本技能将指导你对 Web 应用执行全面的性能审计,以识别瓶颈、优化机会和性能回退。在准备生产部署、调查性能问题或定期进行性能评审时使用。
何时使用此技能
- 进行部署前性能评审
- 调查页面加载缓慢或用户体验不佳的问题
- 定期对现有应用进行性能审计
- 在添加重大功能后验证性能
- 为流量扩展或负载测试做准备
- 优化 Core Web Vitals,以提升 SEO 和用户体验
审计方法
系统化的性能审计分为以下阶段:
阶段 1:初步调查
**目标:**了解应用架构、技术栈和性能涉及范围。
使用 discover 梳理性能关键代码:
discover:
queries:
- id: bundle_imports
type: grep
pattern: "^import.*from ['\"].*['\"]$"
glob: "src/**/*.{ts,tsx,js,jsx}"
- id: database_queries
type: grep
pattern: "(prisma\\.|db\\.|query\\(|findMany|findUnique|create\\(|update\\()"
glob: "**/*.{ts,tsx,js,jsx}"
- id: component_files
type: glob
patterns: ["src/components/**/*.{tsx,jsx}", "src/app/**/*.{tsx,jsx}"]
- id: api_routes
type: glob
patterns: ["src/app/api/**/*.{ts,tsx}", "pages/api/**/*.{ts,tsx}"]
verbosity: files_only
识别关键组件:
- 页面入口点和路由处理程序
- 大型 npm 依赖项(moment.js、lodash 等)
- 数据库访问模式和 ORM 用法
- 图像和媒体资源
- API 端点和数据获取逻辑
- 客户端渲染与服务端组件
阶段 2:包分析
**目标:**识别大型依赖项、代码拆分机会和无用代码。
检查包大小
运行包分析器:
precision_exec:
commands:
- cmd: "npm run build"
timeout_ms: 120000
- cmd: "npx @next/bundle-analyzer"
timeout_ms: 60000
verbosity: standard
常见问题:
- 每个页面都导入的大型依赖项(moment.js、lodash、图表库)
- 同一软件包的多个版本(使用
npm ls <package>检查) - 仍被打包的未使用依赖项(tree-shaking 失效)
- 将 SVG 图标作为组件导入,而不是使用精灵图
- 导入整个 UI 库,而不是单独的组件
查找大型导入
搜索常见的高性能开销软件包:
precision_grep:
queries:
- id: heavy_deps
pattern: "import.*from ['\"](moment|lodash|date-fns|rxjs|@material-ui|antd|chart\\.js)['\"]"
glob: "**/*.{ts,tsx,js,jsx}"
- id: full_library_imports
pattern: "import .* from ['\"](lodash|@mui/material|react-icons)['\"]$"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: locations
优化策略:
不佳 — 导入整个库:
import _ from 'lodash'; // 71KB gzipped
import * as Icons from 'react-icons/fa'; // 500+ icons
良好 — 导入特定模块:
import debounce from 'lodash/debounce'; // 2KB gzipped
import { FaUser, FaCog } from 'react-icons/fa'; // Only what you need
更佳 — 使用现代替代方案:
// Instead of moment.js (72KB), use date-fns (13KB) or native Intl
import { format } from 'date-fns';
// Or native APIs
const formatted = new Intl.DateTimeFormat('en-US').format(date);
检查代码拆分
查找动态导入:
precision_grep:
queries:
- id: dynamic_imports
pattern: "(import\\(|React\\.lazy|next/dynamic)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: large_components
pattern: "export (default )?(function|const).*\\{[\\s\\S]{2000,}"
glob: "src/components/**/*.{tsx,jsx}"
multiline: true
output:
format: files_only
代码拆分模式:
Next.js 动态导入:
import dynamic from 'next/dynamic';
// Client-only components (no SSR)
const ChartComponent = dynamic(() => import('./Chart'), {
ssr: false,
loading: () => <Spinner />,
});
// Route-based code splitting
const AdminPanel = dynamic(() => import('./AdminPanel'));
客户端组件使用 React.lazy:
import { lazy, Suspense } from 'react';
const HeavyModal = lazy(() => import('./HeavyModal'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<HeavyModal />
</Suspense>
);
}
阶段 3:数据库性能
**目标:**消除 N+1 查询、添加缺失的索引并优化查询模式。
检测 N+1 查询模式
搜索循环中的连续查询:
precision_grep:
queries:
- id: potential_n_plus_one
pattern: "(map|forEach).*await.*(findUnique|findFirst|findMany)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: missing_includes
pattern: "findMany\\(\\{[^}]*where[^}]*\\}\\)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: context
context_before: 3
context_after: 3
常见的 N+1 模式:
不佳 — N+1 查询:
const posts = await db.post.findMany();
// Runs 1 query per post!
const postsWithAuthors = await Promise.all(
posts.map(async (post) => ({
...post,
author: await db.user.findUnique({ where: { id: post.authorId } }),
}))
);
良好 — 使用 include 进行预加载:
const posts = await db.post.findMany({
include: {
author: true,
comments: {
include: {
user: true,
},
},
},
});
更佳 — 通过显式 select 仅查询所需字段:
const posts = await db.post.findMany({
select: {
id: true,
title: true,
author: {
select: {
id: true,
name: true,
avatar: true,
},
},
},
});
检查缺失的索引
检查 Prisma 架构中的索引覆盖情况:
precision_read:
files:
- path: "prisma/schema.prisma"
extract: content
verbosity: standard
然后搜索 WHERE 子句:
precision_grep:
queries:
- id: where_clauses
pattern: "where:\\s*\\{\\s*(\\w+):"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: locations
索引优化:
为频繁查询的字段添加索引:
model Post {
id String @id @default(cuid())
title String
published Boolean @default(false)
authorId String
createdAt DateTime @default(now())
// Single column indexes
@@index([authorId])
@@index([createdAt])
// Composite index for common query pattern
@@index([published, createdAt(sort: Desc)])
}
何时添加索引:
- 在 WHERE 子句或联接中使用的外键
- 在 ORDER BY 子句中使用的字段
- 在带分页(基于游标)的 WHERE 查询中使用的字段
- 多字段查询使用的复合索引
何时不添加索引:
- 基数较低的布尔字段(除非作为复合索引的一部分)
- 经常变化的字段(写入会变慢)
- 行数很少的表
优化连接池
检查数据库连接配置:
precision_grep:
queries:
- id: prisma_config
pattern: "PrismaClient\\(.*\\{[\\s\\S]*?\\}"
glob: "**/*.{ts,tsx,js,jsx}"
multiline: true
- id: connection_string
pattern: "connection_limit=|pool_timeout=|connect_timeout="
glob: ".env*"
output:
format: context
context_before: 3
context_after: 3
最佳连接池设置:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
export { prisma };
使用连接池的 DATABASE_URL:
# For serverless (Vercel, AWS Lambda) - use connection pooler
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=10"
# For long-running servers - higher limits
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=20&pool_timeout=30"
阶段 4:渲染性能
**目标:**消除不必要的重新渲染并优化组件渲染。
检测不必要的重新渲染
查找缺少记忆化的情况:
**注意:**这些模式仅用于近似检测单行代码。多行组件定义可能需要手动检查。
precision_grep:
queries:
- id: missing_memo
pattern: "(const \\w+ = \\{|const \\w+ = \\[|const \\w+ = \\(.*\\) =>)(?!.*useMemo)"
glob: "src/components/**/*.{tsx,jsx}"
- id: missing_callback
pattern: "(onChange|onClick|onSubmit)=\\{.*=>(?!.*useCallback)"
glob: "src/components/**/*.{tsx,jsx}"
- id: memo_usage
pattern: "(useMemo|useCallback|React\\.memo)"
glob: "**/*.{tsx,jsx}"
output:
format: files_only
记忆化模式:
不佳 — 每次渲染都会重新创建对象:
function UserProfile({ userId }: Props) {
const user = useUser(userId);
// New object reference on every render!
const config = {
showEmail: true,
showPhone: false,
};
return <UserCard user={user} config={config} />;
}
良好 — 记忆化稳定对象:
function UserProfile({ userId }: Props) {
const user = useUser(userId);
const config = useMemo(
() => ({
showEmail: true,
showPhone: false,
}),
[] // Empty deps - never changes
);
return <UserCard user={user} config={config} />;
}
记忆化回调:
function SearchBar({ onSearch }: Props) {
const [query, setQuery] = useState('');
// Memoize callback to prevent child re-renders
const handleSubmit = useCallback(() => {
onSearch(query);
}, [query, onSearch]);
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
</form>
);
}
记忆化开销较大的组件:
const ExpensiveChart = React.memo(
function ExpensiveChart({ data }: Props) {
// Heavy computation or rendering
return <Chart data={data} />;
},
(prevProps, nextProps) => {
// Custom comparison - only re-render if data changed
return prevProps.data === nextProps.data;
}
);
检查虚拟列表的使用情况
查找未使用虚拟化的大型列表:
precision_grep:
queries:
- id: large_maps
pattern: "\\{.*\\.map\\(.*=>\\s*<(?!Virtualized)(?!VirtualList)"
glob: "src/**/*.{tsx,jsx}"
- id: virtualization
pattern: "(react-window|react-virtualized|@tanstack/react-virtual)"
glob: "package.json"
output:
format: locations
虚拟列表实现:
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Estimated row height
overscan: 5, // Render 5 extra rows for smooth scrolling
});
return (
<div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
>
<ItemRow item={items[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
阶段 5:网络优化
目标: 减少请求瀑布,启用缓存并优化资源交付。
检测请求瀑布
查找顺序获取数据的情况:
precision_grep:
queries:
- id: sequential_fetches
pattern: "await fetch.*\\n.*await fetch"
glob: "**/*.{ts,tsx,js,jsx}"
multiline: true
- id: use_effect_fetches
pattern: "useEffect\\(.*fetch"
glob: "**/*.{tsx,jsx}"
output:
format: context
context_before: 3
context_after: 3
瀑布优化:
不佳 — 顺序请求:
async function loadDashboard() {
const userRes = await fetch('/api/user');
if (!userRes.ok) throw new Error(`Failed to fetch user: ${userRes.status}`);
const user = await userRes.json();
const postsRes = await fetch(`/api/posts?userId=${user.id}`);
if (!postsRes.ok) throw new Error(`Failed to fetch posts: ${postsRes.status}`);
const posts = await postsRes.json();
const commentsRes = await fetch(`/api/comments?userId=${user.id}`);
if (!commentsRes.ok) throw new Error(`Failed to fetch comments: ${commentsRes.status}`);
const comments = await commentsRes.json();
return { user, posts, comments };
}
良好 — 并行请求:
async function loadDashboard(userId: string) {
const [user, posts, comments] = await Promise.all([
fetch(`/api/user/${userId}`).then(async r => {
if (!r.ok) throw new Error(`Failed to fetch user: ${r.status}`);
return r.json();
}),
fetch(`/api/posts?userId=${userId}`).then(async r => {
if (!r.ok) throw new Error(`Failed to fetch posts: ${r.status}`);
return r.json();
}),
fetch(`/api/comments?userId=${userId}`).then(async r => {
if (!r.ok) throw new Error(`Failed to fetch comments: ${r.status}`);
return r.json();
}),
]);
return { user, posts, comments };
}
最佳 — 服务端数据聚合:
// Single API endpoint that aggregates data server-side
async function loadDashboard(userId: string) {
const response = await fetch(`/api/dashboard?userId=${userId}`);
if (!response.ok) {
throw new Error(`Failed to fetch dashboard: ${response.status}`);
}
const data = await response.json();
return data;
}
检查缓存标头
检查缓存配置:
precision_grep:
queries:
- id: cache_headers
pattern: "(Cache-Control|ETag|max-age|stale-while-revalidate)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: next_revalidate
pattern: "(revalidate|force-cache|no-store)"
glob: "src/app/**/*.{ts,tsx}"
output:
format: locations
Next.js 缓存策略:
静态数据(很少更新):
// app/products/page.tsx
export const revalidate = 3600; // Revalidate every hour
export default async function ProductsPage() {
const products = await db.product.findMany();
return <ProductList products={products} />;
}
动态数据(实时):
// app/api/posts/route.ts
export async function GET() {
const posts = await db.post.findMany();
return Response.json(posts, {
headers: {
'Cache-Control': 'private, max-age=60, stale-while-revalidate=300',
},
});
}
退出缓存:
// app/api/user/route.ts
export const dynamic = 'force-dynamic';
export async function GET() {
const user = await getCurrentUser();
return Response.json(user);
}
优化图像加载
检查图像优化情况:
precision_grep:
queries:
- id: img_tags
pattern: "<img\\s+"
glob: "**/*.{tsx,jsx,html}"
- id: next_image
pattern: "(next/image|Image\\s+from)"
glob: "**/*.{tsx,jsx}"
output:
format: files_only
图像优化模式:
不佳 — 未优化的图像:
<img src="/large-photo.jpg" alt="Photo" />
良好 — Next.js Image 组件:
import Image from 'next/image';
<Image
src="/large-photo.jpg"
alt="Photo"
width={800}
height={600}
loading="lazy"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
响应式图像:
<Image
src="/photo.jpg"
alt="Photo"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{ objectFit: 'cover' }}
/>
阶段 6:内存管理
目标: 检测内存泄漏并优化垃圾回收。
查找内存泄漏模式
搜索清理问题:
precision_grep:
queries:
- id: missing_cleanup
pattern: "useEffect\\(.*\\{[^}]*addEventListener(?!.*return.*removeEventListener)"
glob: "**/*.{tsx,jsx}"
multiline: true
- id: interval_leaks
pattern: "(setInterval|setTimeout)(?!.*clear)"
glob: "**/*.{tsx,jsx}"
- id: subscription_leaks
pattern: "subscribe\\((?!.*unsubscribe)"
glob: "**/*.{ts,tsx,js,jsx}"
output:
format: context
context_before: 3
context_after: 3
内存泄漏模式:
不佳 — 事件侦听器泄漏:
useEffect(() => {
window.addEventListener('resize', handleResize);
// Missing cleanup!
}, []);
良好 — 正确清理:
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
不佳 — 定时器泄漏:
useEffect(() => {
const interval = setInterval(() => {
fetchUpdates();
}, 5000);
// Missing cleanup!
}, []);
良好 — 清除定时器:
useEffect(() => {
const interval = setInterval(() => {
fetchUpdates();
}, 5000);
return () => {
clearInterval(interval);
};
}, []);
不佳 — 获取请求未中止:
useEffect(() => {
fetch('/api/data')
.then(r => r.json() as Promise<DataType>)
.then(setData)
.catch(console.error);
// Missing abort on unmount!
}, []);
良好 — AbortController 清理:
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(r => r.json() as Promise<DataType>)
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort();
}, []);
用于缓存的 WeakRef 模式:
class ImageCache {
private cache = new Map<string, WeakRef<HTMLImageElement>>();
get(url: string): HTMLImageElement | undefined {
const ref = this.cache.get(url);
return ref?.deref();
}
set(url: string, img: HTMLImageElement): void {
this.cache.set(url, new WeakRef(img));
}
}
阶段 7:服务端性能
目标: 优化 SSR、流式传输和边缘函数性能。
检查阻塞式服务端组件
查找缓慢的服务端组件:
precision_grep:
queries:
- id: server_components
pattern: "export default async function.*Page"
glob: "src/app/**/page.{tsx,jsx}"
- id: blocking_awaits
pattern: "const.*await.*\\n.*const.*await"
glob: "src/app/**/*.{tsx,jsx}"
multiline: true
output:
format: locations
使用 Suspense 进行流式传输:
不佳 — 阻塞整个页面:
// app/dashboard/page.tsx
export default async function DashboardPage() {
const user = await getUser();
const posts = await getPosts(); // Blocks entire page!
const analytics = await getAnalytics(); // Blocks entire page!
return (
<div>
<UserProfile user={user} />
<PostList posts={posts} />
<Analytics data={analytics} />
</div>
);
}
良好 — 使用 Suspense 进行流式传输:
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default async function DashboardPage() {
// Only block on critical data
const user = await getUser();
return (
<div>
<UserProfile user={user} />
<Suspense fallback={<PostListSkeleton />}>
<PostList />
</Suspense>
<Suspense fallback={<AnalyticsSkeleton />}>
<Analytics />
</Suspense>
</div>
);
}
// Separate component for async data
async function PostList() {
const posts = await getPosts();
return <div>{/* render posts */}</div>;
}
优化边缘函数
检查边缘运行时的使用情况:
precision_grep:
queries:
- id: edge_config
pattern: "export const runtime = ['\"](edge|nodejs)['\"]"
glob: "**/*.{ts,tsx}"
- id: edge_incompatible
pattern: "(fs\\.|path\\.|process\\.cwd)"
glob: "**/api/**/*.{ts,tsx}"
output:
format: locations
边缘运行时最佳实践:
// app/api/geo/route.ts
export const runtime = 'edge';
export async function GET(request: Request) {
// Access edge-specific APIs
const geo = request.headers.get('x-vercel-ip-country');
return Response.json({
country: geo,
timestamp: Date.now(),
});
}
阶段 8:核心 Web 指标
目标: 衡量并优化 LCP、INP 和 CLS。
衡量 Web 指标
添加 Web 指标报告:
// app/layout.tsx or app/web-vitals.tsx
'use client';
import { useEffect } from 'react';
import { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals';
import type { Metric } from 'web-vitals';
function sendToAnalytics(metric: Metric) {
const body = JSON.stringify(metric);
const url = '/api/analytics';
if (navigator.sendBeacon) {
navigator.sendBeacon(url, body);
} else {
fetch(url, { body, method: 'POST', keepalive: true });
}
}
export function WebVitals() {
useEffect(() => {
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
}, []);
return null;
}
优化 LCP(最大内容绘制)
目标:< 2.5s
常见的 LCP 问题:
- 大型主视觉图像未优化
- Web 字体阻塞渲染
- 服务端渲染过慢
- 未对资源进行优先级排序
优化:
// Priority image loading
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={630}
priority // Preload this image!
/>
// Font optimization
<link
rel="preload"
href="/fonts/inter.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
优化 INP(交互到下一次绘制)
目标:< 200ms
常见的 INP 问题:
- 大量 JavaScript 阻塞主线程
- 开销高昂的事件处理程序
- 布局抖动
优化:
// Debounce expensive handlers
import { useDebouncedCallback } from 'use-debounce';
function SearchInput() {
const debouncedSearch = useDebouncedCallback(
(value: string) => {
performSearch(value);
},
300 // Wait 300ms after user stops typing
);
return (
<input
onChange={(e) => debouncedSearch(e.target.value)}
placeholder="Search..."
/>
);
}
优化 CLS(累积布局偏移)
目标:< 0.1
常见的 CLS 问题:
- 图像未指定尺寸
- 广告或嵌入内容未预留空间
- Web 字体导致 FOIT/FOUT
- 动态注入内容
优化:
// Always specify image dimensions
<Image
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
/>
// Reserve space for dynamic content
<div style={{ minHeight: '200px' }}>
<Suspense fallback={<Skeleton height={200} />}>
<DynamicContent />
</Suspense>
</div>
// Use font-display for web fonts
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap; // Prevent invisible text
}
审计报告
按照影响、工作量和优先级整理发现:
报告模板
# Performance Audit Report
## Executive Summary
- **Date:** 2026-02-16
- **Auditor:** Engineer Agent
- **Scope:** Full application performance review
- **Overall Score:** 6.5/10
## Critical Issues (Fix Immediately)
### 1. N+1 Query in Post Listing
- **File:** `src/app/posts/page.tsx`
- **Issue:** Sequential database queries for each post author
- **Impact:** Page load time 3.2s -> should be <500ms
- **Effort:** 15 minutes
- **Fix:** Add `include: { author: true }` to findMany query
## High Priority (Fix This Week)
### 2. Missing Bundle Splitting
- **Files:** `src/app/admin/*`
- **Issue:** Admin panel code (250KB) loaded on all pages
- **Impact:** Initial bundle size 850KB -> should be <200KB
- **Effort:** 1 hour
- **Fix:** Use `next/dynamic` for admin routes
## Medium Priority (Fix This Month)
### 3. Unoptimized Images
- **Files:** Multiple components using `<img>` tags
- **Issue:** LCP of 4.1s due to large unoptimized images
- **Impact:** Poor Core Web Vitals, SEO penalty
- **Effort:** 2 hours
- **Fix:** Migrate to `next/image` with proper sizing
## Low Priority (Backlog)
### 4. Missing Memoization
- **Files:** `src/components/Dashboard/*.tsx`
- **Issue:** Unnecessary re-renders on state changes
- **Impact:** Minor UI lag on interactions
- **Effort:** 3 hours
- **Fix:** Add `useMemo`, `useCallback`, `React.memo`
## Performance Metrics
| Metric | Current | Target | Status |
|--------|---------|--------|--------|
| LCP | 4.1s | <2.5s | FAIL |
| INP | 180ms | <200ms | PASS |
| CLS | 0.05 | <0.1 | PASS |
| Bundle Size | 850KB | <200KB | FAIL |
| API Response | 320ms | <500ms | PASS |
验证脚本
使用验证脚本检查审计是否完整:
bash scripts/validate-performance-audit.sh
脚本会检查:
- 包分析产物
- 数据库查询模式
- 记忆化用法
- 图像优化
- 缓存标头
参考资料
详细的反模式和优化技术请参阅 references/performance-patterns.md。