1. フロントエンドプロジェクトのセットアップ
// Prompt cho Agent Mode:
Create a Next.js 15 frontend project with:
- TypeScript, App Router
- TailwindCSS v4 with custom design tokens
- Shadcn/ui components
- Axios for API calls
- React Hook Form + Zod validation
- Zustand for state management
- Connect to the backend API at localhost:3001
エージェントモードは自動的に実行されます npx create-next-app、依存関係をインストールし、TailwindCSS を構成し、プロジェクト構造を足場にします。
2. コンポーネントの生成 — Vibe コーディングの力
2.1.レイアウトコンポーネント
// Prompt:
Create a responsive dashboard layout with:
- Sidebar navigation (collapsible on mobile)
- Top header with user avatar dropdown
- Main content area with breadcrumbs
- Use TailwindCSS, dark mode support
- Sidebar items: Dashboard, Projects, Tasks, Settings
Copilot は、応答性の高いブレークポイントを含む完全なレイアウトを生成します。
// src/components/layout/DashboardLayout.tsx
'use client';
import { useState } from 'react';
import { Menu, X, ChevronDown } from 'lucide-react';
import Link from 'next/link';
interface DashboardLayoutProps {
children: React.ReactNode;
}
export function DashboardLayout({ children }: DashboardLayoutProps) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const navItems = [
{ href: '/dashboard', label: 'Dashboard', icon: '📊' },
{ href: '/projects', label: 'Projects', icon: '📁' },
{ href: '/tasks', label: 'Tasks', icon: '✅' },
{ href: '/settings', label: 'Settings', icon: '⚙️' },
];
return (
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
{/* Sidebar */}
<aside className={`
fixed inset-y-0 left-0 z-50 w-64 bg-white dark:bg-gray-800
transform transition-transform duration-200 ease-in-out
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
lg:translate-x-0 lg:static lg:inset-0
`}>
<nav className="mt-8 px-4 space-y-2">
{navItems.map((item) => (
<Link key={item.href} href={item.href}
className="flex items-center px-4 py-3 rounded-lg
hover:bg-gray-100 dark:hover:bg-gray-700">
<span className="mr-3">{item.icon}</span>
{item.label}
</Link>
))}
</nav>
</aside>
{/* Main content */}
<main className="flex-1 overflow-y-auto p-6">
{children}
</main>
</div>
);
}
2.2.タスクボードコンポーネント
// Prompt:
Create a Kanban board component for tasks with:
- Columns: Todo, In Progress, In Review, Done
- Drag and drop between columns (use @dnd-kit)
- Task cards showing title, priority badge, assignee avatar, due date
- Add task button per column
- Animate card movement
3. AIによるフォームハンドリング
// Prompt:
Create a task creation form with:
- Title (required, max 255 chars)
- Description (optional, rich text editor)
- Priority select (Low, Medium, High, Urgent)
- Due date picker
- Assignee select (fetch from API)
- Project select
- Use React Hook Form + Zod validation
- Show inline validation errors
- Submit to POST /api/tasks
// src/components/tasks/CreateTaskForm.tsx
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { api } from '@/lib/api';
const taskSchema = z.object({
title: z.string().min(1, 'Title is required').max(255),
description: z.string().optional(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']),
dueDate: z.string().optional(),
assigneeId: z.string().uuid().optional(),
projectId: z.string().uuid(),
});
type TaskFormData = z.infer<typeof taskSchema>;
export function CreateTaskForm({ projectId, onSuccess }: {
projectId: string;
onSuccess: () => void;
}) {
const { register, handleSubmit, formState: { errors, isSubmitting } } =
useForm<TaskFormData>({
resolver: zodResolver(taskSchema),
defaultValues: { projectId, priority: 'MEDIUM' },
});
const onSubmit = async (data: TaskFormData) => {
await api.post('/tasks', data);
onSuccess();
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<input {...register('title')}
placeholder="Task title"
className="w-full px-4 py-2 border rounded-lg" />
{errors.title &&
<p className="text-red-500 text-sm mt-1">{errors.title.message}</p>}
</div>
<div>
<select {...register('priority')} className="w-full px-4 py-2 border rounded-lg">
<option value="LOW">🟢 Low</option>
<option value="MEDIUM">🟡 Medium</option>
<option value="HIGH">🟠 High</option>
<option value="URGENT">🔴 Urgent</option>
</select>
</div>
<button type="submit" disabled={isSubmitting}
className="w-full bg-blue-600 text-white py-2 rounded-lg
hover:bg-blue-700 disabled:opacity-50">
{isSubmitting ? 'Creating...' : 'Create Task'}
</button>
</form>
);
}
4. APIの統合
// Prompt:
Create an API client module with:
- Axios instance with base URL and auth interceptor
- Automatic token refresh on 401
- Type-safe API functions for tasks, projects, auth
- Loading and error state hooks
// src/lib/api.ts
import axios from 'axios';
export const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api',
});
// Auth interceptor
api.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Auto refresh on 401
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
const { data } = await axios.post(
`${api.defaults.baseURL}/auth/refresh`,
{ refreshToken }
);
localStorage.setItem('accessToken', data.accessToken);
error.config.headers.Authorization = `Bearer ${data.accessToken}`;
return api.request(error.config);
}
}
return Promise.reject(error);
}
);
5. Zustandによる状態管理
// Prompt:
Create a Zustand store for task management with:
- Tasks list with CRUD operations
- Filter by status, priority, assignee
- Optimistic updates for status changes
- Integration with the API client
// src/stores/taskStore.ts
import { create } from 'zustand';
import { api } from '@/lib/api';
interface Task {
id: string;
title: string;
status: 'TODO' | 'IN_PROGRESS' | 'IN_REVIEW' | 'DONE';
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
assignee?: { id: string; name: string; avatar?: string };
}
interface TaskStore {
tasks: Task[];
loading: boolean;
fetchTasks: (projectId: string) => Promise<void>;
updateStatus: (taskId: string, status: Task['status']) => Promise<void>;
}
export const useTaskStore = create<TaskStore>((set, get) => ({
tasks: [],
loading: false,
fetchTasks: async (projectId) => {
set({ loading: true });
const { data } = await api.get(`/tasks?projectId=${projectId}`);
set({ tasks: data.data, loading: false });
},
updateStatus: async (taskId, status) => {
// Optimistic update
const previousTasks = get().tasks;
set({
tasks: previousTasks.map((t) =>
t.id === taskId ? { ...t, status } : t
),
});
try {
await api.put(`/tasks/${taskId}`, { status });
} catch {
// Rollback on error
set({ tasks: previousTasks });
}
},
}));
6.レスポンシブデザイン
// Prompt:
Make the task board responsive:
- Desktop: 4-column Kanban layout
- Tablet: 2-column grid with horizontal scroll
- Mobile: single column with tab navigation for each status
- Preserve drag and drop on tablet, disable on mobile
Copilot は、画面サイズに基づいて応答性の高いブレークポイントと条件付きレンダリングを生成します。
7. ダークモード
// Prompt:
Add dark mode toggle with:
- System preference detection
- Manual toggle in header
- Persist preference in localStorage
- Smooth transition animation
8. フロントエンドコンポーネントのテスト
// Prompt:
Write tests for CreateTaskForm component:
- Renders all form fields
- Shows validation errors for empty title
- Submits form data to API
- Disables button while submitting
- Calls onSuccess after successful submit
Use React Testing Library + Jest
// src/components/tasks/__tests__/CreateTaskForm.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CreateTaskForm } from '../CreateTaskForm';
import { api } from '@/lib/api';
jest.mock('@/lib/api');
describe('CreateTaskForm', () => {
const mockOnSuccess = jest.fn();
const projectId = 'test-project-id';
it('shows validation error for empty title', async () => {
render(<CreateTaskForm projectId={projectId} onSuccess={mockOnSuccess} />);
await userEvent.click(screen.getByText('Create Task'));
await waitFor(() => {
expect(screen.getByText('Title is required')).toBeInTheDocument();
});
});
it('submits form data correctly', async () => {
(api.post as jest.Mock).mockResolvedValue({ data: {} });
render(<CreateTaskForm projectId={projectId} onSuccess={mockOnSuccess} />);
await userEvent.type(screen.getByPlaceholderText('Task title'), 'New Task');
await userEvent.click(screen.getByText('Create Task'));
await waitFor(() => {
expect(api.post).toHaveBeenCalledWith('/tasks', expect.objectContaining({
title: 'New Task',
projectId,
priority: 'MEDIUM',
}));
expect(mockOnSuccess).toHaveBeenCalled();
});
});
});
9. Copilot を使用したフロントエンドのデバッグ
実行時エラーが発生した場合:
// Copy error vào chat: I'm getting this error in the browser console: "TypeError: Cannot read properties of undefined (reading 'map')" at TaskBoard.tsx:45
The tasks data from the API might be null on first render. Fix this with proper null checks and loading state.
Copilot はエラーを分析し、根本原因を見つけてコードを修正します。多くの場合、Null チェックを追加したり状態をロードしたりします。
10. まとめ
| タスク | バイブコーディングプロンプト | 出力 |
|---|---|---|
| レイアウト | レイアウト構造の説明 | レスポンシブなダッシュボード |
| コンポーネント | UIの動作を説明する | インタラクティブなコンポーネント |
| フォーム | フィールドと検証の説明 | タイプセーフなフォーム |
| API | エンドポイントの説明 | タイプセーフなクライアント |
| 州 | データフローの説明 | ズスタンド店 |
| テスト | 「X のテストを作成する」 | RTLテストスイート |
フロントエンド Vibe コーディングは、UI コンポーネントが非常に強力であるため、 パターンが何度も繰り返される — AI は反復的な UI コードの生成に優れています。
次の記事: に展開します モバイルとクロスプラットフォーム — ネイティブに反応し、Vibe コーディングでフラッターします。