fe-perf

📁 ingpdw/pdw-fe-dev-tool 📅 6 days ago
1
总安装量
1
周安装量
#50849
全站排名
安装命令
npx skills add https://github.com/ingpdw/pdw-fe-dev-tool --skill fe-perf

Agent 安装分布

mcpjam 1
claude-code 1
replit 1
junie 1
zencoder 1

Skill 文档

FE Performance Optimization

$ARGUMENTS로 전달된 대상의 성능을 분석하고 최적화한다.

분석 절차

  1. 대상 파악: 파일 또는 “bundle” 키워드에 따라 분석 범위를 결정한다
  2. 코드 분석: 성능 안티패턴을 식별한다
  3. 최적화 제안: 우선순위별로 개선안을 제시한다
  4. 적용: 승인된 최적화를 적용한다

성능 최적화 카테고리

1. 렌더링 최적화

불필요한 리렌더링 방지

// 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} />;
}

React.memo 적절한 사용

// 비용이 큰 컴포넌트에만 적용
const ExpensiveList = memo(function ExpensiveList({ items }: Props) {
  return items.map((item) => <ExpensiveItem key={item.id} item={item} />);
});

useMemo / useCallback

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

// memo된 자식에 전달하는 콜백에 useCallback
const handleSelect = useCallback((id: string) => {
  setSelectedId(id);
}, []);

2. 코드 분할 & Lazy Loading

라우트 기반 코드 분할

Next.js App Router는 자동으로 라우트별 코드 분할을 적용한다. 추가 최적화:

// 무거운 컴포넌트 lazy loading
import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
  loading: () => <Skeleton className="h-[400px]" />,
  ssr: false, // 클라이언트 전용
});

조건부 import

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

// Good — 필요할 때만 로드
const PDFViewer = dynamic(() =>
  import("react-pdf").then((mod) => ({ default: mod.PDFViewer }))
);

3. 번들 사이즈 최적화

트리쉐이킹 확인

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

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

// Best — 작은 대안 사용
import { useDebouncedCallback } from "use-debounce";

번들 분석

# Vite 번들 분석
npx vite-bundle-visualizer

# Next.js 번들 분석
ANALYZE=true next build  # @next/bundle-analyzer 필요

무거운 의존성 교체 가이드

무거운 라이브러리 가벼운 대안
moment (300KB) date-fns (트리쉐이킹) 또는 dayjs (2KB)
lodash (70KB) 개별 import 또는 ES 네이티브 메서드
uuid (10KB) crypto.randomUUID() (네이티브)
axios (30KB) fetch (네이티브)
classnames (2KB) clsx (1KB) 또는 cn()

4. 이미지 최적화

// 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";  // 트리쉐이킹 지원

5. Server Component 최적화

// 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. 리스트 가상화

// 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. 캐싱 전략

// 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 }
);

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) < 800ms SSG/ISR, 에지 캐싱

리포트 형식

# Performance Audit: [대상]

## 요약
- 주요 이슈: N개
- 예상 개선 효과: [번들 -NKB, 렌더링 -Nms 등]

## High Impact (우선 적용)
### [P1] 이슈 제목
- **영향**: [번들 사이즈 / 렌더링 / 로딩 속도]
- **현재**: 설명
- **개선안**: 코드

## Medium Impact
...

## Low Impact
...

실행 규칙

  1. 인자가 없으면 사용자에게 분석 대상을 질문한다
  2. “bundle” 인자 시 package.json, build 설정을 분석한다
  3. 과도한 최적화를 경계한다 (측정 가능한 이슈에 집중)
  4. 최적화 전후 차이를 구체적 수치로 설명한다