シニアフロントエンド
React/Next.js アプリケーションのためのフロントエンド開発パターン、パフォーマンス最適化、自動化ツール。
目次
プロジェクトスキャフォールディング
TypeScript、Tailwind CSS、ベストプラクティス設定を備えた新しい Next.js または React プロジェクトを生成します。
ワークフロー: 新しいフロントエンドプロジェクトの作成
-
プロジェクト名とテンプレートを指定してスキャフォルダーを実行します:
python scripts/frontend_scaffolder.py my-app --template nextjs -
オプション機能(認証、API、フォーム、テスト、Storybook)を追加します:
python scripts/frontend_scaffolder.py dashboard --template nextjs --features auth,api -
プロジェクトディレクトリに移動して依存関係をインストールします:
cd my-app && npm install -
開発サーバーを起動します:
npm run dev
スキャフォルダーオプション
| オプション | 説明 |
|---|---|
--template nextjs | App Router と Server Components を備えた Next.js 14+ |
--template react | React + Vite with TypeScript |
--features auth | NextAuth.js 認証を追加 |
--features api | React Query + API クライアントを追加 |
--features forms | React Hook Form + Zod バリデーションを追加 |
--features testing | Vitest + Testing Library を追加 |
--dry-run | ファイルを作成せずにプレビュー |
生成される構造(Next.js)
my-app/
├── app/
│ ├── layout.tsx # フォントを含むルートレイアウト
│ ├── page.tsx # ホームページ
│ ├── globals.css # Tailwind + CSS 変数
│ └── api/health/route.ts
├── components/
│ ├── ui/ # Button, Input, Card
│ └── layout/ # Header, Footer, Sidebar
├── hooks/ # useDebounce, useLocalStorage
├── lib/ # utils (cn), constants
├── types/ # TypeScript インターフェース
├── tailwind.config.ts
├── next.config.js
└── package.json
コンポーネント生成
TypeScript、テスト、Storybook ストーリーを備えた React コンポーネントを生成します。
ワークフロー: 新しいコンポーネントの作成
-
クライアントコンポーネントを生成します:
python scripts/component_generator.py Button --dir src/components/ui -
サーバーコンポーネントを生成します:
python scripts/component_generator.py ProductCard --type server -
テストとストーリーファイルを含めて生成します:
python scripts/component_generator.py UserProfile --with-test --with-story -
カスタムフックを生成します:
python scripts/component_generator.py FormValidation --type hook
ジェネレーターオプション
| オプション | 説明 |
|---|---|
--type client | 'use client' を持つクライアントコンポーネント(デフォルト) |
--type server | 非同期サーバーコンポーネント |
--type hook | カスタム React フック |
--with-test | テストファイルを含める |
--with-story | Storybook ストーリーを含める |
--flat | サブディレクトリなしで出力ディレクトリに作成 |
--dry-run | ファイルを作成せずにプレビュー |
生成されるコンポーネントの例
'use client';
import { useState } from 'react';
import { cn } from '@/lib/utils';
interface ButtonProps {
className?: string;
children?: React.ReactNode;
}
export function Button({ className, children }: ButtonProps) {
return (
<div className={cn('', className)}>
{children}
</div>
);
}
バンドル分析
バンドル最適化の機会を探るために package.json とプロジェクト構造を分析します。
ワークフロー: バンドルサイズの最適化
-
プロジェクトでアナライザーを実行します:
python scripts/bundle_analyzer.py /path/to/project -
ヘルススコアと問題を確認します:
Bundle Health Score: 75/100 (C) HEAVY DEPENDENCIES: moment (290KB) Alternative: date-fns (12KB) or dayjs (2KB) lodash (71KB) Alternative: lodash-es with tree-shaking -
推奨される修正を適用して、重い依存関係を置き換えます。
-
冗長モードで再実行してインポートパターンを確認します:
python scripts/bundle_analyzer.py . --verbose
バンドルスコアの解釈
| スコア | 評価 | アクション |
|---|---|---|
| 90-100 | A | バンドルは適切に最適化されています |
| 80-89 | B | マイナーな最適化が可能です |
| 70-79 | C | 重い依存関係を置き換えてください |
| 60-69 | D | 複数の問題に対処する必要があります |
| 0-59 | F | 重大なバンドルサイズの問題があります |
検出された重い依存関係
アナライザーは、以下の一般的な重いパッケージを特定します:
| パッケージ | サイズ | 代替案 |
|---|---|---|
| moment | 290KB | date-fns (12KB) または dayjs (2KB) |
| lodash | 71KB | tree-shaking を備えた lodash-es |
| axios | 14KB | ネイティブ fetch または ky (3KB) |
| jquery | 87KB | ネイティブ DOM API |
| @mui/material | 大 | shadcn/ui または Radix UI |
React パターン
参照: references/react_patterns.md
複合コンポーネント
関連コンポーネント間で状態を共有します:
const Tabs = ({ children }) => {
const [active, setActive] = useState(0);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
};
Tabs.List = TabList;
Tabs.Panel = TabPanel;
// 使用法
<Tabs>
<Tabs.List>
<Tabs.Tab>One</Tabs.Tab>
<Tabs.Tab>Two</Tabs.Tab>
</Tabs.List>
<Tabs.Panel>Content 1</Tabs.Panel>
<Tabs.Panel>Content 2</Tabs.Panel>
</Tabs>
カスタムフック
再利用可能なロジックを抽出します:
function useDebounce<T>(value: T, delay = 500): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// 使用法
const debouncedSearch = useDebounce(searchTerm, 300);
レンダープロップ
レンダリングロジックを共有します:
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url).then(r => r.json()).then(setData).finally(() => setLoading(false));
}, [url]);
return render({ data, loading });
}
// 使用法
<DataFetcher
url="/api/users"
render={({ data, loading }) =>
loading ? <Spinner /> : <UserList users={data} />
}
/>
Next.js 最適化
参照: references/nextjs_optimization_guide.md
Server と Client コンポーネント
デフォルトでは Server Components を使用します。以下の場合にのみ 'use client' を追加します:
- イベントハンドラー(onClick、onChange)
- 状態(useState、useReducer)
- エフェクト(useEffect)
- ブラウザ API
// Server Component (デフォルト) - 'use client' なし
async function ProductPage({ params }) {
const product = await getProduct(params.id); // サーバー側フェッチ
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={product.id} /> {/* Client component */}
</div>
);
}
// Client Component
'use client';
function AddToCartButton({ productId }) {
const [adding, setAdding] = useState(false);
return <button onClick={() => addToCart(productId)}>Add</button>;
}
画像最適化
import Image from 'next/image';
// ファーストビュー - すぐに読み込む
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
/>
// fill を使用したレスポンシブ画像
<div className="relative aspect-video">
<Image
src="/product.jpg"
alt="Product"
fill
sizes="(max-width: 768px) 100vw, 50vw"
className="object-cover"
/>
</div>
データフェッチングパターン
// 並列フェッチング
async function Dashboard() {
const [user, stats] = await Promise.all([
getUser(),
getStats()
]);
return <div>...</div>;
}
// Suspense を使用したストリーミング
async function ProductPage({ params }) {
return (
<div>
<ProductDetails id={params.id} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={params.id} />
</Suspense>
</div>
);
}
アクセシビリティとテスト
参照: references/frontend_best_practices.md
アクセシビリティチェックリスト
- セマンティック HTML: 適切な要素を使用する(
<button>、<nav>、<main>) - キーボードナビゲーション: すべてのインタラクティブ要素がフォーカス可能
- ARIA ラベル: アイコンや複雑なウィジェットにラベルを提供
- カラーコントラスト: 通常のテキストは最低 4.5:1
- フォーカスインジケーター: 可視フォーカス状態
// アクセシブルなボタン
<button
type="button"
aria-label="Close dialog"
onClick={onClose}
className="focus-visible:ring-2 focus-visible:ring-blue-500"
>
<XIcon aria-hidden="true" />
</button>
// キーボードユーザー向けのスキップリンク
<a href="#main-content" className="sr-only focus:not-sr-only">
Skip to main content
</a>
テスト戦略
// React Testing Library を使用したコンポーネントテスト
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('button triggers action on click', async () => {
const onClick = vi.fn();
render(<Button onClick={onClick}>Click me</Button>);
await userEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
// アクセシビリティのテスト
test('dialog is accessible', async () => {
render(<Dialog open={true} title="Confirm" />);
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby');
});
クイックリファレンス
一般的な Next.js 設定
// next.config.js
const nextConfig = {
images: {
remotePatterns: [{ hostname: "cdnexamplecom" }],
formats: ['image/avif', 'image/webp'],
},
experimental: {
optimizePackageImports: ['lucide-react', '@heroicons/react'],
},
};
Tailwind CSS ユーティリティ
// cn() を使用した条件付きクラス
import { cn } from '@/lib/utils';
<button className={cn(
'px-4 py-2 rounded',
variant === 'primary' && 'bg-blue-500 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)} />
TypeScript パターン
// children を含む Props
interface CardProps {
className?: string;
children: React.ReactNode;
}
// ジェネリックコンポーネント
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>;
}
リソース
- React パターン:
references/react_patterns.md - Next.js 最適化:
references/nextjs_optimization_guide.md - ベストプラクティス:
references/frontend_best_practices.md - Forcing-question ライブラリ (Matt Pocock grill):
references/forcing_questions.md - 構成マップ (どのスペシャリストにフォークするか):
references/composition_map.md
前提条件と検証可能な成功基準(Karpathy 規律)
このスキルがコンポーネントをスキャフォールディングしたり、フレームワークを推奨したり、バンドルを監査したりする前に、以下の 4 つの前提条件を必ず明示しなければなりません。
- 主要ユーザーデバイス + ネットワーク — モバイル 4G、デスクトップ ファイバー、ローエンド Android、またはコーポレートネットワーク。すべてのパフォーマンス決定を左右します。
- LCP ターゲット(ミリ秒) — 「高速」ではなく、単一の数値。バンドル予算とレンダリングの選択を左右します。
- SEO 依存 vs. 認証壁あり — レンダリング方式(SSR/SSG/RSC vs. SPA)を左右します。
- WCAG ターゲット + 指名されたアクセシビリティ責任者 — AA、AAA、またはベストエフォート。アクセシビリティへの投資と CI ゲートを左右します。
検証可能な成功基準 (Karpathy #4) — すべての推奨事項には以下を含める必要があります:
- 主要デバイス上の p75 での Core Web Vitals ターゲット(LCP、INP、CLS)
- ルートごとの KB-gzip 単位の JS バンドル予算
- Lighthouse アクセシビリティフロア + パフォーマンスフロア
これら 3 つのうちのいずれかが明記されていない場合、推奨は不完全です — forcing-question ライブラリの Q2 に戻ってください。
scripts/frontend_decision_engine.py ツールはこれらのチェックをエンコードしています: 4 つの前提条件の入力なしではプロファイルの推奨を拒否し、一致したプロファイルの検証可能なしきい値を出力します。
カスタマイズプロファイル
profiles/ にある 4 つの組み込みプロファイルが、すべての推奨事項を調整します:
| プロファイル | 選択するタイミング | LCP ターゲット (モバイル 4G p75) | バンドル予算 |
|---|---|---|---|
next-app-router | SaaS カスタマーフェーシング、SEO + 動的、RSC ファースト | 2000ms | 150 KB-gzip / ルート |
remix-or-sveltekit | モバイル 4G プライマリ、低 JS ファースト、プログレッシブエンハンスメント | 1500ms | 80 KB-gzip / ルート |
vite-spa | 認証壁ありアプリ、デスクトップ/コーポレートプライマリ | 2500ms | 200 KB 初期 + 80 KB / ルート |
astro-or-static | マーケティング/ドキュメント/ブログ、ほぼ書き込みなし、SEO 重視 | 1200ms | 30 KB JS / ページ |
プロファイルの選択は以下のように行います:
python scripts/frontend_decision_engine.py \
--primary-device mobile-4g --lcp-target-ms 2000 \
--seo-dependent true --auth-walled false --team-size 5
ツールは、最適なプロファイル、次点のトレードオフ(15% 以内の場合)、スタックの選択、そのプロファイルで避けるべきアンチパターン、および必要な CI ゲートを返します。
カスタムプロファイルを追加するには(例: 組織の内部ツールデフォルト): profiles/vite-spa.json を profiles/<your-org>.json にコピーし、constraints と success_thresholds を調整します。
構成マップ
このスキルは、POWERFUL 層のスペシャリストが所有するスコープを再実装しません。代わりにそれらにフォークします。完全なルーティングテーブルについては references/composition_map.md を参照してください。主なフォーク:
| 関心事 | フォーク先 |
|---|---|
| WCAG 監査、コントラスト、スクリーンリーダー | engineering-team/skills/a11y-audit/ |
| バンドルプロファイリング + ランタイムパフォーマンス | engineering/skills/performance-profiler/ |
| 映画的 / スクロールストーリーテリングランディング | engineering-team/skills/epic-design/ |
| Apple HIG (iOS / macOS / visionOS) | product-team/skills/apple-hig-expert/ |
| コミット前 Karpathy レビュー | engineering/karpathy-coder/ |
| 事前設計アーキテクチャグリル | engineering/grill-me/ |
cs-frontend-engineer エージェントは、context: fork を介してこれらのフォークを調整します。別のエージェントから Agent({subagent_type: "cs-frontend-engineer", prompt: "..."}) または /cs:frontend-review で呼び出します。