シニアフロントエンド
React/Next.jsアプリケーション向けのフロントエンド開発パターン、パフォーマンス最適化、および自動化ツール。
目次
プロジェクトスキャフォールディング
TypeScript、Tailwind CSS、ベストプラクティス設定を備えた新しいNext.jsまたはReactプロジェクトを生成します。
ワークフロー:新しいフロントエンドプロジェクトの作成
-
プロジェクト名とテンプレートを指定してスキャフォルダーを実行します:
python scripts/frontend_scaffolder.py my-app --template nextjs -
オプション機能を追加します(auth、api、forms、testing、storybook):
python scripts/frontend_scaffolder.py dashboard --template nextjs --features auth,api -
プロジェクトに移動して依存関係をインストールします:
cd my-app && npm install -
開発サーバーを起動します:
npm run dev
スキャフォルダーオプション
| オプション | 説明 |
|---|---|
--template nextjs | App Routerとサーバーコンポーネントを備えたNext.js 14+ |
--template react | React + Vite(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 -
ヘルススコアと問題を確認します:
バンドルヘルススコア: 75/100 (C) 重い依存関係: moment (290KB) 代替案: date-fns (12KB) または dayjs (2KB) lodash (71KB) 代替案: lodash-es(ツリーシェイキング対応) -
推奨される修正を適用して、重い依存関係を置き換えます。
-
詳細モードで再実行して、インポートパターンを確認します:
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 | 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);
Render Props
レンダリングロジックを共有します:
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
サーバーコンポーネント vs クライアントコンポーネント
デフォルトでサーバーコンポーネントを使用します。'use client' は、次の場合にのみ追加します:
- イベントハンドラ(onClick、onChange)
- 状態(useState、useReducer)
- エフェクト(useEffect)
- ブラウザAPI
// サーバーコンポーネント(デフォルト)- 'use client' なし
async function ProductPage({ params }) {
const product = await getProduct(params.id); // サーバーサイドフェッチ
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={product.id} /> {/* クライアントコンポーネント */}
</div>
);
}
// クライアントコンポーネント
'use client';
function AddToCartButton({ productId }) {
const [adding, setAdding] = useState(false);
return <button onClick={() => addToCart(productId)}>追加</button>;
}
画像最適化
import Image from 'next/image';
// ファーストビュー - 即時読み込み
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
/>
// フィルを使用したレスポンシブ画像
<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="ダイアログを閉じる"
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">
メインコンテンツにスキップ
</a>
テスト戦略
// React Testing Libraryを使用したコンポーネントテスト
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('ボタンがクリック時にアクションをトリガーする', async () => {
const onClick = vi.fn();
render(<Button onClick={onClick}>クリックしてください</Button>);
await userEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
// アクセシビリティのテスト
test('ダイアログがアクセシブルであること', async () => {
render(<Dialog open={true} title="確認" />);
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby');
});
クイックリファレンス
一般的なNext.js設定
// next.config.js
const nextConfig = {
images: {
remotePatterns: [{ protocol: 'https', hostname: 'cdn.example.com' }],
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つの前提条件を必ず明確にする必要があります。
- プライマリユーザーデバイス + ネットワーク — mobile-4G、desktop-fiber、low-end-Android、corporate-network。すべてのパフォーマンス決定を左右します。
- LCP目標(ミリ秒) — 「速い」ではなく、単一の数値。バンドル予算とレンダリング方法の選択を左右します。
- SEO依存 vs. 認証必須 — レンダリング方法(SSR/SSG/RSC vs. SPA)を左右します。
- WCAG目標 + 指名されたa11y責任者 — AA、AAA、またはベストエフォート。アクセシビリティへの投資とCIゲートを左右します。
検証可能な成功基準(Karpathy #4) — すべての推奨事項には以下を含める必要があります:
- プライマリデバイスにおけるp75でのCore Web Vitals目標(LCP、INP、CLS)
- ルートごとのJSバンドル予算(KB-gzip)
- Lighthouseアクセシビリティフロア + パフォーマンスフロア
これらの3つのいずれかが明記されていない場合、その推奨事項は不完全です。forcing-questionライブラリのQ2に戻ってください。
scripts/frontend_decision_engine.py ツールはこれらのチェックをエンコードしています。4つの前提条件の入力なしにプロファイルを推奨することを拒否し、一致したプロファイルの検証可能なしきい値を出力します。
カスタマイズプロファイル
profiles/ にある4つの組み込みプロファイルが、すべての推奨事項を調整します:
| プロファイル | 選択するタイミング | LCP目標(mobile-4G p75) | バンドル予算 |
|---|---|---|---|
next-app-router | SaaS顧客向け、SEO + 動的、RSC優先 | 2000ms | 150 KB-gzip / ルート |
remix-or-sveltekit | Mobile-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: "..."}) を使用して呼び出すか、…