fe-perf

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

FE Performance Optimization

前端性能优化

$ARGUMENTS
로 전달된 대상의 성능을 분석하고 최적화한다.
分析并优化通过
$ARGUMENTS
传递的目标的性能。

분석 절차

分析流程

  1. 대상 파악: 파일 또는 "bundle" 키워드에 따라 분석 범위를 결정한다
  2. 코드 분석: 성능 안티패턴을 식별한다
  3. 최적화 제안: 우선순위별로 개선안을 제시한다
  4. 적용: 승인된 최적화를 적용한다
  1. 确定分析目标:根据文件或"bundle"关键词确定分析范围
  2. 代码分析:识别性能反模式
  3. 优化建议:按优先级提出改进方案
  4. 实施优化:应用已确认的优化方案

성능 최적화 카테고리

性能优化分类

1. 렌더링 최적화

1. 渲染优化

불필요한 리렌더링 방지

避免不必要的重渲染

tsx
// Bad — 매 렌더마다 새 객체 생성
function Parent() {
  return <Child style={{ color: "red" }} data={[1, 2, 3]} />;
}

// Good — 안정적 참조
const style = { color: "red" };
const data = [1, 2, 3];

function Parent() {
  return <Child style={style} data={data} />;
}
tsx
// 不良示例 — 每次渲染都会创建新对象
function Parent() {
  return <Child style={{ color: "red" }} data={[1, 2, 3]} />;
}

// 良好示例 — 稳定的引用
const style = { color: "red" };
const data = [1, 2, 3];

function Parent() {
  return <Child style={style} data={data} />;
}

React.memo 적절한 사용

合理使用React.memo

tsx
// 비용이 큰 컴포넌트에만 적용
const ExpensiveList = memo(function ExpensiveList({ items }: Props) {
  return items.map((item) => <ExpensiveItem key={item.id} item={item} />);
});
tsx
// 仅对性能开销大的组件使用
const ExpensiveList = memo(function ExpensiveList({ items }: Props) {
  return items.map((item) => <ExpensiveItem key={item.id} item={item} />);
});

useMemo / useCallback

useMemo / useCallback

tsx
// 비용이 큰 계산에 useMemo
const sortedItems = useMemo(
  () => items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// memo된 자식에 전달하는 콜백에 useCallback
const handleSelect = useCallback((id: string) => {
  setSelectedId(id);
}, []);
tsx
// 对性能开销大的计算使用useMemo
const sortedItems = useMemo(
  () => items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// 对传递给已缓存子组件的回调使用useCallback
const handleSelect = useCallback((id: string) => {
  setSelectedId(id);
}, []);

2. 코드 분할 & Lazy Loading

2. 代码分割 & 懒加载

라우트 기반 코드 분할

基于路由的代码分割

Next.js App Router는 자동으로 라우트별 코드 분할을 적용한다. 추가 최적화:
tsx
// 무거운 컴포넌트 lazy loading
import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
  loading: () => <Skeleton className="h-[400px]" />,
  ssr: false, // 클라이언트 전용
});
Next.js App Router会自动按路由进行代码分割。额外优化方案:
tsx
// 对重型组件进行懒加载
import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
  loading: () => <Skeleton className="h-[400px]" />,
  ssr: false, // 仅客户端渲染
});

조건부 import

条件式导入

tsx
// Bad — 항상 로드
import { PDFViewer } from "react-pdf";

// Good — 필요할 때만 로드
const PDFViewer = dynamic(() =>
  import("react-pdf").then((mod) => ({ default: mod.PDFViewer }))
);
tsx
// 不良示例 — 始终加载
import { PDFViewer } from "react-pdf";

// 良好示例 — 仅在需要时加载
const PDFViewer = dynamic(() =>
  import("react-pdf").then((mod) => ({ default: mod.PDFViewer }))
);

3. 번들 사이즈 최적화

3. 包体积优化

트리쉐이킹 확인

确认Tree Shaking生效

typescript
// Bad — 전체 라이브러리 import
import _ from "lodash";
_.debounce(fn, 300);

// Good — 개별 함수 import
import debounce from "lodash/debounce";
debounce(fn, 300);

// Best — 작은 대안 사용
import { useDebouncedCallback } from "use-debounce";
typescript
// 不良示例 — 导入整个库
import _ from "lodash";
_.debounce(fn, 300);

// 良好示例 — 导入单个函数
import debounce from "lodash/debounce";
debounce(fn, 300);

// 最佳方案 — 使用轻量替代库
import { useDebouncedCallback } from "use-debounce";

번들 분석

包体积分析

bash
undefined
bash
undefined

Vite 번들 분석

Vite包体积分析

npx vite-bundle-visualizer
npx vite-bundle-visualizer

Next.js 번들 분석

Next.js包体积分析

ANALYZE=true next build # @next/bundle-analyzer 필요
undefined
ANALYZE=true next build # 需要安装@next/bundle-analyzer
undefined

무거운 의존성 교체 가이드

重型依赖替换指南

무거운 라이브러리가벼운 대안
moment
(300KB)
date-fns
(트리쉐이킹) 또는
dayjs
(2KB)
lodash
(70KB)
개별 import 또는 ES 네이티브 메서드
uuid
(10KB)
crypto.randomUUID()
(네이티브)
axios
(30KB)
fetch
(네이티브)
classnames
(2KB)
clsx
(1KB) 또는
cn()
重型依赖库轻量替代方案
moment
(300KB)
date-fns
(支持Tree Shaking)或
dayjs
(2KB)
lodash
(70KB)
单个函数导入或原生ES方法
uuid
(10KB)
crypto.randomUUID()
(原生API)
axios
(30KB)
fetch
(原生API)
classnames
(2KB)
clsx
(1KB)或自定义
cn()
函数

4. 이미지 최적화

4. 图片优化

tsx
// Next.js Image 컴포넌트 사용
import Image from "next/image";

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority          // LCP 이미지에 priority
  placeholder="blur" // 블러 플레이스홀더
  sizes="(max-width: 768px) 100vw, 50vw"  // 반응형 크기
/>

// 아이콘 — SVG inline 또는 sprite
import { LucideIcon } from "lucide-react";  // 트리쉐이킹 지원
tsx
// 使用Next.js Image组件
import Image from "next/image";

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority          // 为LCP图片设置priority
  placeholder="blur" // 模糊占位符
  sizes="(max-width: 768px) 100vw, 50vw"  // 响应式尺寸
/>

// 图标 — 使用内联SVG或雪碧图
import { LucideIcon } from "lucide-react";  // 支持Tree Shaking

5. Server Component 최적화

5. Server Component优化

tsx
// Server Component에서 데이터 가져오기 (제로 번들)
async function ProductList() {
  const products = await db.product.findMany();
  return products.map((p) => <ProductCard key={p.id} product={p} />);
}

// 병렬 데이터 페칭
async function Dashboard() {
  const [users, orders, stats] = await Promise.all([
    getUsers(),
    getOrders(),
    getStats(),
  ]);
  // ...
}
tsx
// 在Server Component中获取数据(零打包体积)
async function ProductList() {
  const products = await db.product.findMany();
  return products.map((p) => <ProductCard key={p.id} product={p} />);
}

// 并行数据获取
async function Dashboard() {
  const [users, orders, stats] = await Promise.all([
    getUsers(),
    getOrders(),
    getStats(),
  ]);
  // ...
}

6. 리스트 가상화

6. 列表虚拟化

tsx
// 100+ 아이템 리스트에 가상화 적용
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,
  });

  return (
    <div ref={parentRef} className="h-[500px] overflow-auto">
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              height: virtualItem.size,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            <ListItem item={items[virtualItem.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}
tsx
// 对100+条目的列表应用虚拟化
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,
  });

  return (
    <div ref={parentRef} className="h-[500px] overflow-auto">
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              height: virtualItem.size,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            <ListItem item={items[virtualItem.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}

7. 캐싱 전략

7. 缓存策略

typescript
// React Query 캐싱
const { data } = useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
  staleTime: 5 * 60 * 1000,    // 5분간 fresh
  gcTime: 30 * 60 * 1000,      // 30분간 캐시 유지
});

// Next.js fetch 캐싱
const data = await fetch("/api/data", {
  next: { revalidate: 3600 },   // 1시간 ISR
});

// Next.js unstable_cache
import { unstable_cache } from "next/cache";
const getCachedData = unstable_cache(
  async () => db.query(),
  ["cache-key"],
  { revalidate: 3600 }
);
typescript
// React Query缓存
const { data } = useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
  staleTime: 5 * 60 * 1000,    // 5分钟内视为新鲜数据
  gcTime: 30 * 60 * 1000,      // 缓存保留30分钟
});

// Next.js fetch缓存
const data = await fetch("/api/data", {
  next: { revalidate: 3600 },   // 1小时重新验证(ISR)
});

// Next.js unstable_cache
import { unstable_cache } from "next/cache";
const getCachedData = unstable_cache(
  async () => db.query(),
  ["cache-key"],
  { revalidate: 3600 }
);

8. Web Vitals 체크포인트

8. Web Vitals检查点

지표목표최적화 포인트
LCP (Largest Contentful Paint)< 2.5s히어로 이미지 priority, 폰트 preload
FID/INP (Interaction to Next Paint)< 200ms무거운 연산 분리, useDeferredValue
CLS (Cumulative Layout Shift)< 0.1이미지 width/height 지정, 스켈레톤
TTFB (Time to First Byte)< 800msSSG/ISR, 에지 캐싱
指标目标值优化要点
LCP (Largest Contentful Paint)< 2.5s英雄图设置priority、预加载字体
FID/INP (Interaction to Next Paint)< 200ms分离重型计算、使用useDeferredValue
CLS (Cumulative Layout Shift)< 0.1为图片指定宽高、使用骨架屏
TTFB (Time to First Byte)< 800ms使用SSG/ISR、边缘缓存

리포트 형식

报告格式

markdown
undefined
markdown
undefined

Performance Audit: [대상]

性能审计报告: [分析目标]

요약

摘要

  • 주요 이슈: N개
  • 예상 개선 효과: [번들 -NKB, 렌더링 -Nms 등]
  • 主要问题:N个
  • 预期优化效果:[包体积减少NKB、渲染速度提升Nms等]

High Impact (우선 적용)

高优先级(优先实施)

[P1] 이슈 제목

[P1] 问题标题

  • 영향: [번들 사이즈 / 렌더링 / 로딩 속도]
  • 현재: 설명
  • 개선안: 코드
  • 影响范围:[包体积 / 渲染 / 加载速度]
  • 当前状态:问题描述
  • 优化方案:代码示例

Medium Impact

中优先级

...
...

Low Impact

低优先级

...
undefined
...
undefined

실행 규칙

执行规则

  1. 인자가 없으면 사용자에게 분석 대상을 질문한다
  2. "bundle" 인자 시
    package.json
    , build 설정을 분석한다
  3. 과도한 최적화를 경계한다 (측정 가능한 이슈에 집중)
  4. 최적화 전후 차이를 구체적 수치로 설명한다
  1. 若无参数,则询问用户确定分析目标
  2. 当参数为"bundle"时,分析
    package.json
    和构建配置
  3. 避免过度优化,聚焦可量化的性能问题
  4. 用具体数值说明优化前后的差异