performance について
Web アプリ開発者向けのパフォーマンス改善スキルです。フロントエンド、バックエンド、データベースの測定方法と、一般的な最適化手法を例とともに示します。
主な機能
- 01Web Vitals とバックエンドの目標値
- 02Lighthouse と k6 による測定
- 03コード分割とツリーシェイキング
- 04React の再レンダリングとリストの最適化
- 05データベースクエリとインデックスの調整
- 06Redis と Next.js のキャッシュ
活用シーン
- 01Lighthouse で Web アプリを監査
- 02k6 で API の負荷テストを実施
- 03React の再レンダリングとバンドルサイズを削減
- 04遅いクエリを特定してインデックスを追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ motormetrics/ motormetrics --skill performanceインストール後、Claude Code で/performanceを入力して読み込まれたか確認します。
詳細
パフォーマンススキル
パフォーマンス目標
フロントエンド(Web Vitals): LCP < 2.5s、FID < 100ms、CLS < 0.1、FCP < 1.8s、TTFB < 600ms バックエンド: 応答時間 < 500ms(p95)、コールドスタート < 2s、エラー率 < 1% データベース: クエリ時間 < 100ms、キャッシュヒット率 > 80%
パフォーマンスの測定
# Lighthouse audit
npx lighthouse https://motormetrics.app --view
# Bundle analysis
cd apps/web && ANALYZE=true pnpm build
# Load test with k6
k6 run --vus 100 --duration 5m load-test.js
バンドルサイズの最適化
動的インポート
// ❌ Static import (loads immediately)
import { HeavyComponent } from "./heavy-component";
// ✅ Dynamic import (lazy load)
import dynamic from "next/dynamic";
const HeavyComponent = dynamic(() => import("./heavy-component"), {
loading: () => <div>Loading...</div>,
ssr: false,
});
ツリーシェイキング
// ❌ Imports entire library
import _ from "lodash";
// ✅ Import only what you need
import uniq from "lodash/uniq";
React のパフォーマンス
再レンダリングの防止
// useMemo for expensive calculations
const processed = useMemo(() => expensiveOperation(data), [data]);
// useCallback for stable function references
const handleClick = useCallback(() => doSomething(), []);
// React.memo for pure components
const Child = React.memo(function Child({ name }) {
return <div>{name}</div>;
});
長いリストの仮想化
import { FixedSizeList } from "react-window";
<FixedSizeList height={600} itemCount={items.length} itemSize={100} width="100%">
{({ index, style }) => <Item style={style} data={items[index]} />}
</FixedSizeList>
データベースクエリの最適化
インデックスの追加
// packages/database/src/schema/cars.ts
export const cars = pgTable("cars", {
make: text("make").notNull(),
month: text("month").notNull(),
}, (table) => ({
makeIdx: index("cars_make_idx").on(table.make),
}));
N+1 クエリの回避
// ❌ N+1 queries
for (const post of posts) {
post.author = await db.query.users.findFirst({ where: eq(users.id, post.authorId) });
}
// ✅ Single query with relation
const posts = await db.query.posts.findMany({ with: { author: true } });
必要な列のみを選択
// ❌ Selects all columns
const users = await db.query.users.findMany();
// ✅ Select specific columns
const users = await db.select({ id: users.id, name: users.name }).from(users);
ページネーションの使用
const cars = await db.query.cars.findMany({
limit: 20,
offset: (page - 1) * 20,
});
キャッシュ
Redis キャッシュ
import { redis } from "@motormetrics/utils/redis";
export async function getCarsWithCache(make: string) {
const cacheKey = `cars:${make}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached as string);
const cars = await db.query.cars.findMany({ where: eq(cars.make, make) });
await redis.set(cacheKey, JSON.stringify(cars), { ex: 3600 });
return cars;
}
Next.js のキャッシュ
// Revalidate every hour
export const revalidate = 3600;
// Or use fetch with caching
const data = await fetch(url, { next: { revalidate: 3600 } });
画像の最適化
import Image from "next/image";
// ✅ Optimized image with priority for above-fold
<Image
src="/hero.jpg"
alt="Hero"
fill
sizes="(max-width: 768px) 100vw, 50vw"
priority
/>
Vercel Function の最適化
// API route with optimized config
export const config = {
maxDuration: 60, // seconds
};
// Use edge functions for low-latency endpoints
export const runtime = "edge";
プロファイリング
パフォーマンスミドルウェア
// apps/api/src/middleware/performance.ts
export const performanceMiddleware = async (c: Context, next: Next) => {
const start = performance.now();
await next();
const duration = performance.now() - start;
c.header("X-Response-Time", `${Math.round(duration)}ms`);
if (duration > 1000) {
log.warn("Slow request", { path: c.req.path, duration: Math.round(duration) });
}
};
クエリのプロファイリング
const start = performance.now();
const result = await db.query.cars.findMany();
const duration = performance.now() - start;
if (duration > 100) {
console.warn(`Slow query: ${duration}ms`);
}
Vercel Analytics
Vercel Dashboard → Analytics で以下を確認します。
- 関数の実行時間
- エッジの応答時間
- Web Vitals の指標
- リクエスト数とエラー
負荷テスト
// load-test.js (k6)
import http from "k6/http";
import { check } from "k6";
export const options = {
stages: [
{ duration: "2m", target: 100 },
{ duration: "5m", target: 100 },
{ duration: "2m", target: 0 },
],
thresholds: {
http_req_duration: ["p(95)<500"],
http_req_failed: ["rate<0.01"],
},
};
export default function() {
const res = http.get("https://api.motormetrics.app/api/v1/cars/makes");
check(res, { "status 200": (r) => r.status === 200 });
}
パフォーマンスチェックリスト
- バンドルサイズ < 200KB(初回読み込み時)
- LCP < 2.5s、FID < 100ms、CLS < 0.1
- API の応答 < 500ms
- データベースクエリ < 100ms
- 画像を最適化(WebP/AVIF)
- コード分割を実装
- キャッシュ戦略を導入
- 長いリストを仮想化
- 圧縮を有効化
すぐにできる改善
- 圧縮:gzip/brotli を有効化(サイズを 60~80% 削減)
- 画像の最適化:Next.js Image コンポーネントを使用
- キャッシュ:負荷の高い処理を Redis にキャッシュ
- インデックス:頻繁にクエリされる列にインデックスを追加
- コード分割:負荷の高いコンポーネントを遅延読み込み
- メモ化:負荷の高い処理に useMemo/useCallback を使用
参考資料
- Web Vitals: https://web.dev/vitals
- Next.js Performance: https://nextjs.org/docs/app/building-your-application/optimizing
- k6 Load Testing: https://k6.io/docs
コメント
Markdown 対応