【モダン開発】Cursor・Vercel・Supabaseで爆速ビルドするClaude 3.5 Sonnet搭載AIアプリ構築ガイド
現在のWebアプリケーション開発は、「AIにコードを書かせ、強力なマネージドサービスを組み合わせて即座にデプロイする」という時代に突入しています。その中心にあるのが、AIネイティブなコードエディターCursor、高速なフロントエンド配信プラットフォームVercel、そして高機能なBaaSであるSupabaseの3つです。
本記事では、これらのモダンな開発ツール群と、最高峰のLLMであるClaude 3.5 Sonnetを組み合わせ、実用的なAIアプリケーションを最短で構築する手順を、具体的なソースコードを交えて徹底的に解説します。
1. システムアーキテクチャと使用ツール
今回構築するアプリケーションは、ユーザーの入力に対してClaude 3.5 Sonnetがリアルタイムに回答を生成し、その履歴をデータベースに保存する「AIチャット・アシスタント」です。アーキテクチャの概要は以下の通りです。
- フロントエンド / API: Next.js (App Router) + Tailwind CSS
- AIモデル: Claude 3.5 Sonnet (Anthropic Claude API)
- データベース: Supabase (PostgreSQL / Row Level Security対応)
- エディター: Cursor (Composer機能をフル活用)
- ホスティング: Vercel
Next.jsの公式ドキュメントやベストプラクティスについては、Next.js 公式ドキュメントを適宜参照してください。
2. Supabaseのセットアップとデータベース設計
まずはデータの保存先となるSupabaseのプロジェクトを作成し、データベースの準備を行います。Supabase 公式ドキュメントに沿って、アカウント作成と新規プロジェクトの作成を完了させてください。
2.1. テーブルの作成
プロジェクトが作成できたら、SQL Editorを開き、チャット履歴を保存するための chats テーブルを作成します。以下のSQLを実行してください。
-- チャット履歴を格納するテーブル
create table chats (
id uuid default gen_random_uuid() primary key,
user_message text not null,
ai_response text not null,
created_at timestamp with time zone default timezone('utc'::text, now()) not null
);
-- セキュリティ用のRow Level Security (RLS)を有効化
alter table chats enable row level security;
-- 簡易的な読み書きポリシー(本番環境では認証ユーザーのみにするなどの制限を推奨)
create policy "Allow public read and write access" on chats
for all using (true) with check (true);
SQLの実行が完了すると、chats テーブルがデータベース内に構築されます。
3. CursorとClaude 3.5 Sonnetを活用したNext.jsアプリ開発
次に、Cursorを開き、Next.jsプロジェクトをセットアップします。Cursorは、エディター自体にClaude 3.5 Sonnetが組み込まれており、チャット(Ctrl+K または Cmd+K)やComposer(Ctrl+I または Cmd+I)を使って指示を出すだけで、ファイル生成から修正まで自動で行ってくれます。
3.1. プロジェクトの初期化とライブラリ導入
任意のディレクトリで以下のコマンドを実行し、Next.jsアプリを作成します。
npx create-next-app@latest ai-chat-app --typescript --tailwind --app
cd ai-chat-app
次に、必要なパッケージ(SupabaseクライアントとAnthropic SDK)をインストールします。
npm install @supabase/supabase-js @anthropic-ai/sdk
3.2. Supabaseクライアントの設定
Cursorで lib/supabaseClient.ts を新規作成し、以下のコードを記述します。Cursorのチャットに「Supabaseクライアントを初期化するコードを書いて」と指示するだけで、即座に以下と同等のコードが出力されます。
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || '';
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
if (!supabaseUrl || !supabaseAnonKey) {
console.warn('Supabase env variables are missing!');
}
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
3.3. Claude 3.5 Sonnetを呼び出すAPI Routeの実装
Next.jsのRoute Handlerを使って、フロントエンドからのリクエストを受け取り、Claude 3.5 Sonnetを呼び出して結果をSupabaseに保存するAPIを作成します。
app/api/chat/route.ts を作成し、以下のコードを記述します。
import { NextResponse } from 'next/server';
import Anthropic from '@anthropic-ai/sdk';
import { supabase } from '@/lib/supabaseClient';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY || '',
});
export async function POST(request: Request) {
try {
const { message } = await request.json();
if (!message) {
return NextResponse.json({ error: 'Message is required' }, { status: 400 });
}
// 1. Claude 3.5 Sonnet API を呼び出し
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20240620',
max_tokens: 1024,
messages: [{ role: 'user', content: message }],
});
// 安全にレスポンスのテキストを取り出す
const aiResponseText = response.content[0].type === 'text'
? response.content[0].text
: '応答の取得に失敗しました。';
// 2. 結果をSupabaseに保存
const { error: dbError } = await supabase
.from('chats')
.insert([{ user_message: message, ai_response: aiResponseText }]);
if (dbError) {
console.error('Database save error:', dbError);
// DB保存失敗でもAI応答は返すためにエラーはスローしない
}
return NextResponse.json({ response: aiResponseText });
} catch (error: any) {
console.error('API Error:', error);
return NextResponse.json({ error: error.message || 'Internal Server Error' }, { status: 500 });
}
}
3.4. フロントエンド(UI)の実装
app/page.tsx を修正し、簡単なチャットUIを構築します。この時も、CursorのComposer機能で「入力フォームがあり、回答をストックし、過去のチャット履歴もSupabaseから取得して表示する美しい Tailwind CSS のUIを作って」と依頼すれば、Claude 3.5 Sonnetが完璧なコードを瞬時に出力してくれます。
'use client';
import { useState, useEffect } from 'react';
import { supabase } from '@/lib/supabaseClient';
export default function Home() {
const [message, setMessage] = useState('');
const [loading, setLoading] = useState(false);
const [history, setHistory] = useState<any[]>([]);
// 履歴の読み込み
const fetchHistory = async () => {
const { data, error } = await supabase
.from('chats')
.select('*')
.order('created_at', { ascending: false });
if (!error && data) {
setHistory(data);
}
};
useEffect(() => {
fetchHistory();
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!message.trim()) return;
setLoading(true);
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message }),
});
const data = await res.json();
if (data.response) {
setMessage('');
fetchHistory(); // 履歴を再ロード
} else {
alert('エラーが発生しました: ' + data.error);
}
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
return (
<main className="max-w-3xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-6 text-center text-gray-800">Claude 3.5 Chat App</h1>
<form onSubmit={handleSubmit} className="mb-8 flex gap-2">
<input
type="text"
className="border rounded p-3 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 text-black"
placeholder="AIに質問する..."
value={message}
onChange={(e) => setMessage(e.target.value)}
disabled={loading}
/>
<button
type="submit"
className="bg-blue-600 text-white px-6 py-3 rounded hover:bg-blue-700 transition disabled:bg-blue-300"
disabled={loading}
>
{loading ? '送信中...' : '送信'}
</button>
</form>
<div className="space-y-4">
<h2 className="text-xl font-semibold border-b pb-2 text-gray-700">チャット履歴</h2>
{history.length === 0 ? (
<p className="text-gray-500 text-center py-4">履歴はありません</p>
) : (
history.map((item) => (
<div key={item.id} className="p-4 bg-gray-50 rounded-lg shadow-sm border border-gray-100">
<p className="font-bold text-blue-600">Q: {item.user_message}</p>
<p className="mt-2 text-gray-800 whitespace-pre-wrap">A: {item.ai_response}</p>
</div>
))
)}
</div>
</main>
);
}
4. 開発をさらに強化するインフラの選択肢
今回はフロントエンドと簡単なバックエンドAPIをVercelに集約してデプロイしますが、システムが拡大するにつれて、長時間の非同期ジョブ、定期的なスクレイピング、バックグラウンドでのAIエージェントの常時駆動などが必要になってきます。
そのような場合は、Vercelのサーバーレス関数の実行制限(タイムアウト)を回避するため、専用のVPS上でDockerや自作AIエージェントをデプロイ・稼働させるアプローチが非常に有効です。
⚡ 初期費用無料・ドメインが2個無料
例えば、ConoHa VPSなどの高速でスケールしやすい仮想専用サーバーを活用すれば、自作のエージェントプログラムを24時間低コストで安定運用させることができます。詳しいVPS上での常時稼働エージェント構築手順については、以下の記事も参考にしてください。
5. Vercelへのデプロイと環境変数の設定
開発したNext.jsプロジェクトをGitHubにプッシュし、Vercelと連携させます。
- GitHubリポジトリを作成し、ローカルコードをコミット&プッシュします。
- Vercelにログインし、「Add New」>「Project」から該当のGitHubリポジトリを選択します。
- Environment Variables(環境変数) に、以下の3つの変数を追加します。
NEXT_PUBLIC_SUPABASE_URL: SupabaseのProject URLNEXT_PUBLIC_SUPABASE_ANON_KEY: SupabaseのAPIキー(anon / public)ANTHROPIC_API_KEY: Anthropicのダッシュボードから取得したClaude APIキー
- 「Deploy」ボタンをクリックすれば、わずか数分でインターネット上に独自のAIアプリが公開されます。
6. よくあるエラーとトラブルシューティング
CursorやVercelを用いた開発において、初学者が直面しやすい代表的なエラーと解決策を解説します。
6.1. SupabaseのRLSによる「New row violates row-level security policy」エラー
- 原因: SupabaseのテーブルでRow Level Security(RLS)が有効化されているにもかかわらず、データを挿入するための適切な「ポリシー(Policy)」が設定されていない場合に発生します。
- 解決方法: Supabaseの管理画面(Dashboard) -> Database -> Policies に移動し、
chatsテーブルに対して「挿入(Insert)」「参照(Select)」を許可するポリシーが正しく作成されているか確認してください。開発段階では、すべてのユーザー(Public)に対して許可する設定を追加することで解消できます(本番環境では適切に制限してください)。
6.2. Vercelでのデプロイ時の「TypeScript Error (Build Failed)」
- 原因: Cursorが自動生成したコードの中で、型定義が曖昧な部分(例:
any型の多用や、環境変数がundefinedになる可能性の考慮漏れ)があり、Next.jsのビルドプロセスでTypeScriptのチェックに引っかかるためです。 - 解決方法: 環境変数を読み込む部分で
|| ''のようにデフォルトの空文字列を設定するか、next.config.jsでビルド時のTSチェックを一時的にバイパスさせる(非推奨)か、Cursorに「TypeScriptの型エラーが出ているので修正して」と該当ファイルを渡して修正指示を出すことで解決します。
6.3. APIの「504 Gateway Timeout」エラー(Vercel)
- 原因: Vercelの無料プラン(Hobby)では、サーバーレス関数の実行制限時間が最大10秒に制限されています。Claude 3.5 Sonnetの応答生成がこの10秒を超えると、Vercel側からタイムアウトエラーが返されます。
- 解決方法: Next.jsのRoute Handlerで Streaming(ストリーミングレスポンス) を実装するか、長時間の処理が避けられない場合は前述のようにVercelの関数ではなくVPS等の常時稼働サーバーへバックエンドをオフロードすることを検討してください。
7. まとめ
Cursor、Vercel、Supabase、そしてClaude 3.5 Sonnetの組み合わせは、個人開発者からスタートアップのプロトタイピングまで、現代における最も強力かつ高速な開発スタックの1つです。
Cursorを使うことでコーディングの摩擦は極限まで減少し、Supabaseによってバックエンドの実装コストがほぼゼロになり、Vercelがデプロイと運用の手間を最小化してくれます。ぜひこのスタックを活用し、あなたのアイデアを形にしてみてください!