mcpField
P
performance-audit

performance-audit

github.com/mgd34msu测试与质量659 次安装10 周前更新
代码仓库

关于 performance-audit

performance-audit 是面向开发者的 Web 应用性能审查技能,按阶段检查资源包、数据库、渲染、网络和内存等方面。

主要功能

  1. 01分阶段审查 Web 性能
  2. 02分析资源包与依赖
  3. 03检查数据库查询和索引
  4. 04审查渲染与重复渲染
  5. 05检查网络请求与缓存

适用场景

  1. 01部署前审查性能
  2. 02调查页面加载缓慢
  3. 03检查重大功能上线后的性能
  4. 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。

评论

支持 Markdown