近年、生成AI(Generative AI)は、さまざまな分野で革新的な応用が進んでおり、テキスト生成、画像生成、音声生成など、多岐にわたる能力を発揮しています。一方で、Next.jsはReactを基盤としたフレームワークで、効率的なWebアプリケーション構築を可能にします。
本記事では、Next.jsと生成AIを組み合わせたアプリケーションの開発手法について、試してみた知見を共有したいと思います。
Next.js 15
Gemini model gemini-1.5-flash
[目次を開く]
料金について
今回、利用するgemini-1.5-flashをはじめすべてのモデルが無料で使えるようです。以下のような無料枠が設けられています。

ちょっとした検証などするには無料で十分ですね。
また無料枠の上限以上の利用が必要な場合は従量課金での利用もできるみたいです。

設定しないと課金情報はセットされないので基本的にはいつの間にか料金が発生するということはないと思いますが、ご利用される方は、公式の情報をよく読んでご注意ください。
以下、料金についての公式ページです。
APIキーの取得
先ずはGeminiをAPIで利用するためにAPIキーを取得しましょう。
以下のURLからGoogle AI Studioにアクセスしてください。
(Googleアカウントでのログインが必要です)
ページを開くと新しいプロンプトの画面が表示されるので左上の「Get API Key」をクリックしてください。

すると「キーAPIキーを作成」(若干、日本語があやしいですが。。。)というボタンがあるので、そちらをクリックしてください。

作成したAPIキーはGoogle AI Studioでいつでも確認できます。Webやローカルで保存する場合、取扱には十分注意してください。
Next.jsのセットアップ
以下のコマンドでプロジェクトを作成していきましょう。
# next-app-geminiというプロジェクトを作成
npx create-next-app@latest next-app-gemini --use-npm --ts --tailwind --eslint --app --src-dir
npx create-next-app@latest
のあとに続いているのは、TypeScript、tailwind 、eslint 、App Router、srcディレクトリを利用するオプションです。
必要なライブラリをインストールする
npm install @google/generative-ai
これでGemini APIを使うためのNext.jsのセットアップが完了しました。
Gemini APIを使ってみる
では、簡単なUIとAPIを実装して実際に使ってみましょう。
以下、ディレクトリ構成です。
/app
/api
/generate
route.ts
/page.tsx
※プロジェクトのルートに.env.local
を作成しAPIキーを定義してください。
# .env.local
GEMINI_API_KEY={your api key}
page.tsxにUIを実装
'use client';
import { useState } from 'react';
interface RecipeResponse {
recipeName: string;
ingredients: string[];
steps: string[];
}
export default function HomePage() {
const [prompt, setPrompt] = useState<string>(''); // プロンプト
const [result, setResult] = useState<RecipeResponse | null>(null); // 結果をオブジェクトで管理
const [loading, setLoading] = useState<boolean>(false); // ローディング状態
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setResult(null);
try {
// APIリクエスト送信
const response = await fetch('/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ input: prompt }),
});
const data = await response.json();
console.log('Response:', data);
if (response.ok && data.response) {
// レスポンスから文字列形式のJSONを抽出してパース
const jsonString = data.response.match(/```json\n([\s\S]*?)\n```/)?.[1];
if (jsonString) {
const parsedResult: RecipeResponse = JSON.parse(jsonString);
setResult(parsedResult);
} else {
console.error('Failed to parse JSON from response.');
setResult(null);
}
} else {
console.error(`Error: ${data.error}`);
setResult(null);
}
} catch (error) {
console.error('Error:', error);
setResult(null);
} finally {
setLoading(false);
}
};
return (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1>料理手順生成デモ</h1>
<form onSubmit={handleSubmit} style={{ marginBottom: '20px' }}>
<textarea
rows={4}
style={{ width: '100%', marginBottom: '10px', padding: '10px' }}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="料理名を入力してください"
/>
<button
type="submit"
disabled={loading}
style={{
padding: '10px 20px',
background: loading ? '#ccc' : '#0070f3',
color: 'white',
border: 'none',
cursor: loading ? 'not-allowed' : 'pointer',
}}
>
{loading ? '生成中...' : '生成'}
</button>
</form>
{/* 結果を表示 */}
{result ? (
<div>
<h2>料理名: {result.recipeName}</h2>
<h3>材料:</h3>
<ul style={{ paddingLeft: '20px' }}>
{result.ingredients.map((ingredient: any, index: number) => (
<li key={index} style={{ marginBottom: '5px' }}>
{ingredient}
</li>
))}
</ul>
<h3>手順:</h3>
<ol style={{ paddingLeft: '20px' }}>
{result.steps.map((step, index) => (
<li key={index} style={{ marginBottom: '10px' }}>
{index + 1}: {step}
</li>
))}
</ol>
</div>
) : (
!loading && <p></p>
)}
</div>
);
}
layout.tsxを編集
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja">
<body>
{children}
</body>
</html>
);
}
route.tsを編集
import { NextResponse } from "next/server";
import { GoogleGenerativeAI } from "@google/generative-ai";
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error("APIキーを設定してください");
}
const genAI = new GoogleGenerativeAI(apiKey);
export async function POST(request: Request) {
console.log('Request:', request);
try {
const { input } = await request.json();
const prompt = `
以下のJSONスキーマを使用して、料理名 "${input}" の材料と手順を記述してください。応答は日本語で記述し、正確に表現してください:
{
"recipeName": string,
"ingredients": Array<string>,
"steps": Array<string>
}
例:
{
"recipeName": "カレー",
"ingredients": [
"玉ねぎ 2個",
"肉 200g",
"カレー粉 大さじ2",
"水 500ml",
"野菜(にんじん、じゃがいもなど) 適量"
],
"steps": [
"玉ねぎをみじん切りにして炒める。",
"肉を加えて火が通るまで炒める。",
"カレー粉を加え、水を注いで煮込む。",
"野菜を加え、さらに煮込む。",
"ご飯にかけて完成。"
]
}
`;
// モデルを取得
const model = genAI.getGenerativeModel({
model: "gemini-1.5-flash",
});
// プロンプトを生成AIに送信
const result = await model.generateContent(prompt);
console.log('Result:', result);
// 結果をレスポンスとして返却
return NextResponse.json({
response: result.response.text(),
});
} catch (error: any) {
console.error("Error generating content:", error.message);
return NextResponse.json(
{
error: error.message || "An unexpected error occurred.",
},
{ status: 500 }
);
}
}
※公式のリファレンスにあったコードをもとにしています。
料理名を入力しレシピをステップバイステップで表示してくれるデモを作成してみました。

まとめ
こういう形で構造化データを定義して展開できると考えると、すごい可能性を感じますね。工夫次第で実用性のあるアプリができそうです。みなさんも、ぜひ、オリジナルのWebアプリを作成してみてください!