Next.js × AI アプリテンプレート — 30分でAIアプリを公開
約8分で読めます
Next.js × AI アプリテンプレート — 30分でAIアプリを公開
Next.js + Vercel AI SDK + Claude APIの組み合わせは、AIアプリを最速で構築・公開する王道スタックだ。本記事ではゼロからAIチャットアプリを構築し、Vercelにデプロイするまでの全手順を30分で完了するテンプレートを提供する。
テクノロジースタック比較
| 構成 | 開発速度 | 本番品質 | コスト | 学習コスト |
|---|---|---|---|---|
| Next.js + Vercel AI SDK | 最速 | 高 | 低 | 低 |
| React + 自作API | 遅い | 中 | 低 | 高 |
| Streamlit (Python) | 速い | 低 | 低 | 最低 |
| Flutter + Firebase | 遅い | 高 | 中 | 高 |
プロジェクト作成
# テンプレートから作成
npx create-next-app@latest ai-chat --typescript --tailwind --app --eslint
cd ai-chat
# 必要パッケージ追加
npm install ai @anthropic-ai/sdk
# 環境変数設定
echo "ANTHROPIC_API_KEY=sk-ant-xxxxx" > .env.local
APIルートの実装
ストリーミングチャットAPI
// app/api/chat/route.ts
import Anthropic from '@anthropic-ai/sdk';
import { AnthropicStream, StreamingTextResponse } from 'ai';
const client = new Anthropic();
export async function POST(req: Request) {
const { messages } = await req.json();
const response = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 2048,
stream: true,
system: 'あなたは親切な日本語AIアシスタントです。簡潔に回答してください。',
messages: messages.map((m: { role: string; content: string }) => ({
role: m.role as 'user' | 'assistant',
content: m.content,
})),
});
const stream = AnthropicStream(response);
return new StreamingTextResponse(stream);
}
レート制限ミドルウェア
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const rateLimit = new Map<string, { count: number; resetAt: number }>();
export function middleware(request: NextRequest) {
if (!request.nextUrl.pathname.startsWith('/api/chat')) {
return NextResponse.next();
}
const ip = request.headers.get('x-forwarded-for') || 'unknown';
const now = Date.now();
const windowMs = 60 * 1000; // 1分
const maxRequests = 20;
const current = rateLimit.get(ip);
if (!current || now > current.resetAt) {
rateLimit.set(ip, { count: 1, resetAt: now + windowMs });
return NextResponse.next();
}
if (current.count >= maxRequests) {
return NextResponse.json(
{ error: 'レート制限を超えました。1分後に再試行してください。' },
{ status: 429 }
);
}
current.count++;
return NextResponse.next();
}
フロントエンドUI
チャットコンポーネント
// app/page.tsx
'use client';
import { useChat } from 'ai/react';
import { useState } from 'react';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
});
return (
<main className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">AI Chat</h1>
{/* メッセージ一覧 */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((m) => (
<div
key={m.id}
className={`p-3 rounded-lg ${
m.role === 'user'
? 'bg-blue-100 ml-auto max-w-[80%]'
: 'bg-gray-100 mr-auto max-w-[80%]'
}`}
>
<p className="text-sm font-semibold mb-1">
{m.role === 'user' ? 'あなた' : 'AI'}
</p>
<p className="whitespace-pre-wrap">{m.content}</p>
</div>
))}
{isLoading && (
<div className="bg-gray-100 p-3 rounded-lg mr-auto">
<p className="animate-pulse">考え中...</p>
</div>
)}
</div>
{/* 入力フォーム */}
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="メッセージを入力..."
className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
送信
</button>
</form>
</main>
);
}
Vercelへのデプロイ
# Vercel CLIでデプロイ
npm install -g vercel
vercel
# 環境変数を設定
vercel env add ANTHROPIC_API_KEY
# 本番デプロイ
vercel --prod
機能拡張: ファイルアップロード対応
// app/api/chat-with-file/route.ts
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
export async function POST(req: Request) {
const formData = await req.formData();
const message = formData.get('message') as string;
const file = formData.get('file') as File | null;
const content: Anthropic.MessageCreateParams['messages'][0]['content'] = [];
if (file && file.type.startsWith('image/')) {
const bytes = await file.arrayBuffer();
const base64 = Buffer.from(bytes).toString('base64');
content.push({
type: 'image',
source: { type: 'base64', media_type: file.type as any, data: base64 },
});
}
content.push({ type: 'text', text: message });
const response = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 2048,
messages: [{ role: 'user', content }],
});
return Response.json({ text: response.content[0].text });
}
コスト試算
月間アクティブユーザー数別のAPI コスト見積もり。
| MAU | 平均リクエスト/ユーザー | 月間トークン | 月間コスト(Sonnet) | Vercel費用 |
|---|---|---|---|---|
| 100 | 50 | 5M | ~$25 | 無料 |
| 1,000 | 30 | 30M | ~$150 | 無料 |
| 10,000 | 20 | 200M | ~$1,000 | $20/月 |
| 100,000 | 10 | 1B | ~$5,000 | $150/月 |
デプロイまでのタイムライン
| ステップ | 所要時間 | 内容 |
|---|---|---|
| プロジェクト作成 | 3分 | create-next-app + パッケージ追加 |
| APIルート実装 | 5分 | ストリーミングチャットAPI |
| フロントエンドUI | 10分 | チャットコンポーネント |
| ローカルテスト | 5分 | 動作確認 |
| Vercelデプロイ | 5分 | vercel —prod |
| 合計 | 28分 | 本番公開完了 |
関連記事
A
Agentive 編集部
AIエージェントを実際に使い倒す個人開発者。サイト制作の自動化を実践しながら、その知見を発信しています。