Agentive
ツールレビュー

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費用
100505M~$25無料
1,0003030M~$150無料
10,00020200M~$1,000$20/月
100,000101B~$5,000$150/月

デプロイまでのタイムライン

ステップ所要時間内容
プロジェクト作成3分create-next-app + パッケージ追加
APIルート実装5分ストリーミングチャットAPI
フロントエンドUI10分チャットコンポーネント
ローカルテスト5分動作確認
Vercelデプロイ5分vercel —prod
合計28分本番公開完了

関連記事

A

Agentive 編集部

AIエージェントを実際に使い倒す個人開発者。サイト制作の自動化を実践しながら、その知見を発信しています。