mcpField
F
Frontend Builder

Frontend Builder

github.com/daffy0208開発38215 回インストール44 週間前に更新
リポジトリ

Frontend Builder について

Frontend Builder は、React と Next.js のフロントエンド開発者向けのスキルガイドです。コンポーネント設計、状態管理、データ取得、フォーム、性能改善の実践方法と、フレームワーク選定の指針を紹介します。

主な機能

  1. 01React と Next.js の選定指針
  2. 02再利用可能なコンポーネント設計
  3. 03状態管理の判断ガイド
  4. 04データ取得とフォーム処理
  5. 05レンダリングとアセットの最適化

活用シーン

  1. 01新規フロントエンドのフレームワーク選定
  2. 02保守しやすい UI コンポーネントの構成
  3. 03アプリの状態管理方式の選択
  4. 04フロントエンドの読み込みと描画の改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/daffy0208/ai-dev-standards --skill frontend-builder

インストール後、Claude Code で/frontend-builderを入力して読み込まれたか確認します。

同梱ファイルmanifest.yamlREADME.md

詳細

フロントエンドビルダー

保守性とパフォーマンスに優れた React および Next.js のフロントエンドを構築します。

基本原則

1. コンポーネントの構成

UI を小さく、再利用可能で、単一の目的を持つコンポーネントに分割します。

2. 状態の局所化

状態は、できるだけ使用する場所の近くに保持します。

3. デフォルトで高いパフォーマンス

レンダリング、コード分割、アセットの読み込みを最適化します。

4. 開発者体験

分かりやすい命名、一貫したパターン、役立つエラーを心がけます。

フレームワークの選択

React(Vite)と Next.js の比較

React + Vite を使う場合:

  • クライアント側のみのアプリケーション
  • SEO 要件がない
  • シンプルなデプロイ(静的ホスティング)
  • 初期セットアップをより迅速に行いたい

Next.js を使う場合:

  • SEO が重要(マーケティングサイト、ブログ、e コマース)
  • サーバーサイドレンダリングが必要
  • API ルートが必要
  • ファイルベースのルーティングを使いたい
  • 画像の最適化が重要

ほとんどのプロジェクトにおすすめ:Next.js(App Router)


コンポーネントアーキテクチャ

コンポーネントの種類

1. ページコンポーネント(ルートのエントリーポイント):

// app/users/page.tsx (Next.js App Router)
export default function UsersPage() {
  return (
    <div>
      <Header />
      <UserList />
      <Footer />
    </div>
  )
}

2. 機能コンポーネント(ビジネスロジック):

// components/features/UserList.tsx
export function UserList() {
  const { data, isLoading } = useUsers()

  if (isLoading) return <LoadingSpinner />

  return (
    <div>
      {data.map(user => <UserCard key={user.id} user={user} />)}
    </div>
  )
}

3. UI コンポーネント(再利用可能で、ビジネスロジックを持たない):

// components/ui/button.tsx
export function Button({ children, variant = 'primary', ...props }) {
  return (
    <button
      className={cn(buttonVariants[variant])}
      {...props}
    >
      {children}
    </button>
  )
}
コンポーネントのベストプラクティス
// ✅ Good: Small, focused, typed
interface UserProfileProps {
  user: User
  onEdit?: () => void
}

export function UserProfile({ user, onEdit }: UserProfileProps) {
  return (
    <div className="flex gap-4">
      <Avatar src={user.avatar} alt={user.name} />
      <UserDetails user={user} />
      {onEdit && <Button onClick={onEdit}>Edit</Button>}
    </div>
  )
}

// ❌ Bad: Giant, untyped, unclear
export function UserProfile(props) {
  // 500 lines of JSX, multiple responsibilities
  return <div>...</div>
}

状態管理

判断フロー
How many components need this state?
│
├─ One component → useState
├─ Parent + children → Props or useState + props
├─ Siblings → Lift to common parent
├─ Widely used (theme, auth) → Context API
└─ Complex app state → Zustand or Redux
ローカル状態(useState)
// For component-level state
function Counter() {
  const [count, setCount] = useState(0)
  const [isOpen, setIsOpen] = useState(false)

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}
Context API
// For app-wide state (theme, auth, user)
const UserContext = createContext<UserContextType | undefined>(undefined)

export function UserProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null)

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  )
}

export function useUser() {
  const context = useContext(UserContext)
  if (!context) throw new Error('useUser must be within UserProvider')
  return context
}
Zustand(複雑な状態管理におすすめ)
import { create } from 'zustand'

interface CounterStore {
  count: number
  increment: () => void
  decrement: () => void
  reset: () => void
}

export const useCounterStore = create<CounterStore>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}))

// Usage
function Counter() {
  const { count, increment } = useCounterStore()
  return <button onClick={increment}>{count}</button>
}

データ取得

React Query(おすすめ)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

// Query (GET)
function Users() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    staleTime: 5 * 60 * 1000 // 5 minutes
  })

  if (isLoading) return <LoadingSpinner />
  if (error) return <ErrorMessage error={error} />

  return <UserList users={data} />
}

// Mutation (POST, PUT, DELETE)
function CreateUser() {
  const queryClient = useQueryClient()

  const mutation = useMutation({
    mutationFn: createUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] })
    }
  })

  return (
    <button onClick={() => mutation.mutate({ name: 'John' })}>
      Create User
    </button>
  )
}
Next.js サーバーコンポーネント(App Router)
// app/users/page.tsx
// Server Component - fetches on server
export default async function UsersPage() {
  const users = await fetchUsers() // Runs on server

  return <UserList users={users} />
}

// Client Component - for interactivity
'use client'

export function UserList({ users }: { users: User[] }) {
  const [selected, setSelected] = useState<string | null>(null)

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onClick={() => setSelected(user.id)}
        />
      ))}
    </div>
  )
}

フォーム処理

React Hook Form(おすすめ)
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters')
})

type LoginForm = z.infer<typeof loginSchema>

function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting }
  } = useForm<LoginForm>({
    resolver: zodResolver(loginSchema)
  })

  const onSubmit = async (data: LoginForm) => {
    await login(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          {...register('email')}
          type="email"
          placeholder="Email"
          className="border p-2"
        />
        {errors.email && (
          <span className="text-red-500">{errors.email.message}</span>
        )}
      </div>

      <div>
        <input
          {...register('password')}
          type="password"
          placeholder="Password"
          className="border p-2"
        />
        {errors.password && (
          <span className="text-red-500">{errors.password.message}</span>
        )}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  )
}

スタイリング

Tailwind CSS(おすすめ)
// Install: @shadcn/ui for component library
function Button({ variant = 'primary', children, ...props }) {
  return (
    <button
      className={cn(
        'px-4 py-2 rounded font-medium transition-colors',
        {
          'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
          'bg-gray-200 text-gray-900 hover:bg-gray-300': variant === 'secondary',
          'bg-red-500 text-white hover:bg-red-600': variant === 'danger'
        }
      )}
      {...props}
    >
      {children}
    </button>
  )
}
CSS Modules(代替手段)
// Button.module.css
.button {
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
}

.primary {
  background-color: blue;
  color: white;
}

// Button.tsx
import styles from './Button.module.css'

export function Button({ variant = 'primary', children }) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  )
}

パフォーマンスの最適化

React の最適化
import { memo, useMemo, useCallback } from 'react'

// 1. Memoize expensive calculations
function DataTable({ data }) {
  const sortedData = useMemo(
    () => data.sort((a, b) => a.name.localeCompare(b.name)),
    [data]
  )

  return <Table data={sortedData} />
}

// 2. Memoize callbacks
function Parent() {
  const handleClick = useCallback(() => {
    console.log('Clicked')
  }, [])

  return <ExpensiveChild onClick={handleClick} />
}

// 3. Memoize components
const ExpensiveChild = memo(function ExpensiveChild({ onClick }) {
  return <button onClick={onClick}>Click</button>
})
Next.js の最適化
// 1. Image optimization
import Image from 'next/image'

<Image
  src="/photo.jpg"
  alt="Photo"
  width={500}
  height={300}
  priority // Above the fold
/>

// 2. Font optimization
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({ children }) {
  return (
    <html className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

// 3. Dynamic imports (code splitting)
import dynamic from 'next/dynamic'

const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <LoadingSpinner />
})

エラー処理

エラーバウンダリー
'use client'

import { Component, ReactNode } from 'react'

interface Props {
  children: ReactNode
  fallback?: ReactNode
}

interface State {
  hasError: boolean
  error?: Error
}

export class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error }
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="p-4 bg-red-50 border border-red-200">
          <h2 className="text-red-800">Something went wrong</h2>
          <p className="text-red-600">{this.state.error?.message}</p>
        </div>
      )
    }

    return this.props.children
  }
}
Next.js のエラー処理
// app/error.tsx
'use client'

export default function Error({
  error,
  reset
}: {
  error: Error
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

フォルダー構成

Next.js App Router
app/
├── (auth)/              # Route group (auth pages)
│   ├── login/
│   └── signup/
├── (dashboard)/         # Route group (dashboard)
│   ├── layout.tsx
│   ├── page.tsx
│   └── settings/
├── api/                 # API routes
│   └── users/
│       └── route.ts
└── layout.tsx           # Root layout

components/
├── ui/                  # shadcn/ui components
│   ├── button.tsx
│   ├── input.tsx
│   └── dialog.tsx
├── features/            # Feature components
│   ├── UserList.tsx
│   └── UserProfile.tsx
└── layouts/             # Layout components
    ├── Header.tsx
    └── Footer.tsx

lib/
├── utils.ts             # Utility functions
├── api.ts               # API client
└── validation.ts        # Zod schemas

hooks/
├── useUser.ts
└── useDebounce.ts

stores/
└── userStore.ts         # Zustand stores

TypeScript のベストプラクティス

// 1. Type component props
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger'
  children: ReactNode
  onClick?: () => void
}

export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>
}

// 2. Type API responses
interface User {
  id: string
  name: string
  email: string
}

async function fetchUsers(): Promise<User[]> {
  const res = await fetch('/api/users')
  return res.json()
}

// 3. Type state
const [user, setUser] = useState<User | null>(null)
const [isLoading, setIsLoading] = useState<boolean>(false)

まとめ

優れたフロントエンドでは、次の点を実践します。

  • ✅ ほとんどのプロジェクトで Next.js を使用する(SEO、パフォーマンス、開発者体験)
  • ✅ UI を小さく、型付けされたコンポーネントに分割する
  • ✅ 適切な状態管理を選ぶ(useState → Context → Zustand)
  • ✅ サーバー状態には React Query を使用する
  • ✅ Tailwind CSS + shadcn/ui でスタイリングする
  • ✅ メモ化とコード分割で最適化する
  • ✅ エラーバウンダリーでエラーを適切に処理する
  • ✅ 一貫したフォルダー構成に従う

関連リソース

関連スキル:

  • api-designer - 利用するバックエンド API の設計
  • ux-designer - 実装する UX デザインの作成
  • deployment-advisor - Next.js/React アプリのホスティング

関連パターン:

  • META/DECISION-FRAMEWORK.md - フロントエンドフレームワークの選択
  • STANDARDS/architecture-patterns/component-patterns.md - コンポーネント設計パターン(作成時)

関連プレイブック:

  • PLAYBOOKS/setup-nextjs-project.md - Next.js プロジェクトのセットアップ(作成時)
  • PLAYBOOKS/optimize-frontend-performance.md - パフォーマンスの最適化(作成時)

コメント