Next.jsとOpenAI API-DALL·E 3で画像生成アプリを作ってみた(Tailwind CSS対応)

Next.jsとOpenAI API-DALL·E 3で画像生成アプリを作ってみた(Tailwind CSS対応)
  • URLをコピーしました!

最近話題の 画像生成AI を、実際に自分のアプリに組み込んでみたいと思ったことはありませんか?

この記事では、Next.js(App Router)Tailwind CSS、そして OpenAIの画像生成API(DALL·E 3) を使って、シンプルな画像生成アプリを構築する方法を紹介します。

もし「自分でコードを書くのはちょっと面倒…」という方や、
「すぐに動くUI付きテンプレートが欲しい」という方のために、以下のプラットフォームでテンプレートも販売しています。

本記事で紹介しているテンプレートを販売中!

このチャットボットUIテンプレートは、複数のプラットフォームで販売中です。
「まずは動くものを試したい」「環境構築なしですぐに使いたい」「コード全体を確認したい」
そんな方にぴったりの構成になっています。

ほかのテンプレートも各マイページからチェックできます:

目次

使用している技術について

この画像生成アプリは、以下の技術をベースに構築されています。

Next.js(App Router構成)

Reactベースのフレームワークで、ページやAPIルートを統一的に構築できます。src/app を起点としたApp Router構成を使っています。

Tailwind CSS

ユーティリティファーストなCSSフレームワークで、クラスを使って効率よくデザインを整えられます。レスポンシブでシンプルなUIが素早く作れます

OpenAI API(Image Generation)

OpenAI の /v1/images/generations エンドポイントを使用して、プロンプトに基づいた画像を生成します。
モデル(例:dall-e-3)、画像サイズ、画質などは .env で柔軟に変更できるようになっています。

API Routes + fetch

Next.js の API ルート /api/image を通じて、クライアントと OpenAI API の通信をサーバー側で中継します。
クライアント側は fetch("/api/image") で POST するだけのシンプルな構成です。

OpenAI API keyを作成

今回は、OpenAI APIを利用しますので、OpenAI API keyを作成する必要があります。
OpenAI APIのダッシュボードでAPI keysに遷移します。「Create new secret key」を選択します。

Projectは、任意に選んでもらい、Permissionsを使いたいモデルに合わせて選択するか、Allを選択して生成します。

作成が終わると「sk-」から始まる文字列が作成されるので、それをこの後利用します。
くれぐれもこのkeyが漏れないように注意してください。

環境構築

まずは Next.js の開発環境を用意します。

プロジェクトの作成

プロジェクトを作成していきます。
基本的には、全てデフォルトのままでいいかなと思いますが、必要に応じて変更してもらえれば問題ないです。
(今回のプロジェクトは、Tailwind CSSを利用しますので、Yesにしておくと良いかと思います。デフォルトはYesです。)

$ npx create-next-app@latest image-generator-starter --typescript
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like your code inside a `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to use Turbopack for `next dev`? … No / Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes
Creating a new Next.js app in /image-generator-starter.

Using npm.

Initializing project with template: app-tw 


Installing dependencies:
- react
- react-dom
- next

Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- @tailwindcss/postcss
- tailwindcss
- eslint
- eslint-config-next
- @eslint/eslintrc


added 336 packages, and audited 337 packages in 16s

137 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Initialized a git repository.

Success! Created image-generator-starter at /image-generator-starter

「Success! Created image-generator-starter at /image-generator-starter」が出ていれば、プロジェクト作成は完了です。
プロジェクトが作成できたら、ディレクトリは移動しておきましょう。

cd image-generator-starter

OpenAI APIキーを設定

ルートに .env ファイルを作成します。

OPENAI_API_KEY=sk-...
OPENAI_MODEL=dall-e-3
OPENAI_IMAGE_SIZE=1024x1024
OPENAI_IMAGE_QUALITY=standard

簡単な説明:

  • OPENAI_API_KEY:OpenAI の画像生成 API にアクセスするための秘密鍵(OpenAI の管理画面で取得)
  • OPENAI_MODEL:使用する画像生成モデル。現在は dall-e-3を指定(将来のアップデートにも対応可能)
  • OPENAI_IMAGE_SIZE:生成される画像のサイズ(例:1024x1024)。必要に応じて変更可能
  • OPENAI_IMAGE_QUALITY:画像の品質。standard または hd を選択できます(hd は高品質だが時間がかかることも)

開発サーバーを起動

下記のコマンドを実行することで、ひな形が起動します。

npm run dev

UIの実装(画像生成画面)

画像生成のユーザーインターフェースは、src/app/page.tsx に集約する形にしています。
ソースコードの全量は下記になります。

"use client";

import { useState } from "react";
import Image from "next/image";

export default function Home() {
  // State for user input (image prompt)
  // ユーザーが入力した画像生成プロンプト
  const [prompt, setPrompt] = useState("");

  // State to store generated image URL
  // 生成された画像の URL
  const [imageUrl, setImageUrl] = useState("");

  // Loading indicator state
  // 読み込み中フラグ
  const [loading, setLoading] = useState(false);

  // Error message state
  // エラーメッセージ格納用
  const [error, setError] = useState("");

  // Handle image generation request
  // 画像生成リクエスト処理
  const generateImage = async () => {
    if (!prompt.trim()) return;

    // Reset state before request
    // リクエスト前に状態を初期化
    setLoading(true);
    setError("");
    setImageUrl("");

    try {
      // Send request to internal API
      // 内部API(/api/image)にリクエスト送信
      const res = await fetch("/api/image", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ prompt }),
      });

      const data = await res.json();

      if (res.ok && data.imageUrl) {
        setImageUrl(data.imageUrl);
      } else {
        setError(data.error || "Failed to generate image.");
      }
    } catch (e) {
      console.error("Error:", e);
      setError("An unexpected error occurred.");
    } finally {
      setLoading(false);
    }
  };

  return (
    <main className="min-h-screen bg-gray-900 text-gray-100 flex items-center justify-center px-4 py-8">
      <div className="w-full max-w-xl bg-gray-800 rounded-xl shadow-lg p-6 space-y-6">
        {/* Title / タイトル */}
        <h1 className="text-2xl font-bold text-center text-white">
          AI Image Generator
        </h1>

        {/* Prompt Input / プロンプト入力欄 */}
        <div className="space-y-2">
          <label
            htmlFor="prompt"
            className="block text-sm font-medium text-gray-300"
          >
            Enter your image prompt
          </label>
          <textarea
            id="prompt"
            rows={3}
            value={prompt}
            onChange={(e) => setPrompt(e.target.value)}
            className="w-full p-3 rounded-lg bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-green-400 resize-none"
            placeholder="e.g. A futuristic cityscape at sunset, digital art"
          />
        </div>

        {/* Generate Button / 生成ボタン */}
        <button
          onClick={generateImage}
          disabled={loading}
          className="w-full py-3 rounded-lg bg-green-500 hover:bg-green-600 text-black font-semibold disabled:opacity-50 transition"
        >
          {loading ? "Generating..." : "Generate Image"}
        </button>

        {/* Error Message / エラー表示 */}
        {error && (
          <div className="text-red-400 text-sm text-center">{error}</div>
        )}

        {/* Image Display / 画像表示 */}
        {imageUrl && (
          <div className="mt-4 relative w-full h-[512px]">
            <Image
              src={imageUrl}
              alt="Generated result"
              fill
              unoptimized
              className="rounded-lg object-contain border border-gray-700"
            />
          </div>
        )}
      </div>
    </main>
  );
}

メッセージの状態管理

const [prompt, setPrompt] = useState("");
const [imageUrl, setImageUrl] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");

簡単な説明:

  • prompt:ユーザーが入力した画像生成の説明(プロンプト)を保持します
  • imageUrl:生成された画像のURLを保持します
  • loading:API応答待ちの状態を示します
  • error:エラー発生時に表示するメッセージを保持します

画像生成の送信処理と API 呼び出し

const generateImage = async () => {
  if (!prompt.trim()) return;

  setLoading(true);
  setError("");
  setImageUrl("");

  try {
    const res = await fetch("/api/image", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ prompt }),
    });

    const data = await res.json();

    if (res.ok && data.imageUrl) {
      setImageUrl(data.imageUrl);
    } else {
      setError(data.error || "Failed to generate image.");
    }
  } catch (e) {
    console.error("Error:", e);
    setError("An unexpected error occurred.");
  } finally {
    setLoading(false);
  }
};

簡単な説明:

  • 入力されたプロンプトを /api/image に送信します
  • OpenAI API から画像が返れば URL をセットし、画面に表示されます
  • エラー発生時はメッセージを表示します

入力フォームとボタン(Tailwind CSS)

<textarea
  id="prompt"
  rows={3}
  value={prompt}
  onChange={(e) => setPrompt(e.target.value)}
  className="w-full p-3 rounded-lg bg-gray-700 border border-gray-600 text-white focus:outline-none focus:ring-2 focus:ring-green-400 resize-none"
  placeholder="e.g. A cat surfing on waves, digital art"
/>

<button
  onClick={generateImage}
  disabled={loading}
  className="w-full py-3 rounded-lg bg-green-500 hover:bg-green-600 text-black font-semibold disabled:opacity-50 transition"
>
  {loading ? "Generating..." : "Generate Image"}
</button>

簡単な説明:

  • テキストエリアに画像の説明文を入力
  • ボタンをクリックすると画像生成が始まります(loading 中はボタンが無効になります)
  • Tailwind CSS によって、見た目をシンプルかつ使いやすく整えています

画像表示・エラー・ローディング表示

{error && (
  <div className="text-red-400 text-sm text-center">{error}</div>
)}

{imageUrl && (
  <div className="mt-4 relative w-full h-[512px]">
    <Image
      src={imageUrl}
      alt="Generated result"
      layout="fill"
      className="rounded-lg object-contain border border-gray-700"
    />
  </div>
)}

簡単な説明:

  • error がある場合は赤文字で表示されます
  • 画像が生成された場合は画面内にレスポンシブ表示されます(next/image を使用して最適化)

サーバー側の実装(OpenAI API 連携)

画像生成のやりとりは、クライアントから /api/image にリクエストを送り、
サーバー側で OpenAI の Chat API に中継・レスポンスを返すという流れです。

サーバー側のソースコードの全量は下記になります。

// src/app/api/image/route.ts
import { NextRequest, NextResponse } from "next/server";

// POST handler for image generation using OpenAI API
// OpenAI API を使って画像を生成する POST ハンドラー
export async function POST(req: NextRequest) {
  // Parse the prompt from the request body
  // リクエストボディからプロンプトを取得
  const { prompt } = await req.json();

  // Validate input
  // プロンプトが指定されているかをチェック
  if (!prompt) {
    return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
  }

  try {
    // Load configuration from environment variables (with fallback values)
    // 環境変数から設定を読み込む(なければデフォルト値)
    const model = process.env.OPENAI_MODEL || "dall-e-3";
    const size = process.env.OPENAI_IMAGE_SIZE || "1024x1024";
    const quality = process.env.OPENAI_IMAGE_QUALITY || "standard";

    // Send request to OpenAI's image generation endpoint
    // OpenAI の画像生成エンドポイントへリクエストを送信
    const response = await fetch(
      "https://api.openai.com/v1/images/generations",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          model, // e.g., "dall-e-3"
          prompt, // User's prompt for image generation
          size, // e.g., "1024x1024"
          quality, // "standard" or "hd"
          n: 1, // Number of images to generate
        }),
      }
    );

    const data = await response.json();

    // Handle API error response
    // API エラーが返された場合の処理
    if (data.error) {
      console.error("OpenAI API error:", data.error);
      return NextResponse.json(
        { error: "Image generation failed" },
        { status: 500 }
      );
    }

    // Extract the generated image URL
    // 生成された画像の URL を取得
    const imageUrl = data.data?.[0]?.url;

    // Return the image URL to the client
    // クライアントに画像 URL を返す
    return NextResponse.json({ imageUrl });
  } catch (error) {
    // Handle unexpected server errors
    // 想定外のサーバーエラーを処理
    console.error("Server error:", error);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 }
    );
  }
}

route.ts の基本構成

import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const { prompt } = await req.json();

簡単な説明:

  • POST メソッドに限定した API ルートです。
  • クライアント側から送られた画像生成プロンプト(prompt)を受け取ります。

OpenAI API へのリクエスト

const response = await fetch("https://api.openai.com/v1/images/generations", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: process.env.OPENAI_MODEL || "dall-e-3",
      prompt,
      size: process.env.OPENAI_IMAGE_SIZE || "1024x1024",
      quality: process.env.OPENAI_IMAGE_QUALITY || "standard",
      n: 1,
    }),
  });

  const data = await response.json();

簡単な説明:

  • 環境変数から APIキー, モデル名, 画像サイズ, 画質 を動的に読み込みます。
  • OpenAI の画像生成 API(/v1/images/generations)にプロンプトを渡し、画像生成リクエストを送信します。
  • n: 1 は「1枚だけ生成する」という指定です。

レスポンス処理とエラーハンドリング

  if (data.error) {
    console.error(data.error);
    return NextResponse.json({ error: "Image generation failed" }, { status: 500 });
  }

  const imageUrl = data.data?.[0]?.url;
  return NextResponse.json({ imageUrl });
}

簡単な説明:

  • OpenAI API が失敗した場合は、エラー内容をコンソールに出力し、500 エラーで返します。
  • 正常に画像 URL が返ってきた場合は、それをクライアントに返します(imageUrl)。

【おまけ】注意点

  • APIキーは絶対に公開しないように .env で管理してください。
    仮に漏洩してしまった場合は、OpenAIの管理画面で無効化・再発行を行いましょう。
  • モデル名、画像サイズ、画質は .env で動的に切り替えられるように設計されています。
    利用用途やコストに応じて調整してください(例:standard より hd は高品質&高コスト)。
  • 画像の保存期間や利用可能時間が限られている場合があるため、必要であれば取得後すぐに保存処理を行う設計も検討してください。

動作確認

簡単に動作確認をしておきましょう!

ブラウザで、「localhost:3000」を開くと、下記のような画面が表示されます。

試しに、「a cute cat」と入力してみましょう。

しばらく待っていると、かわいらしい猫の画像が生成されて表示されました!
日本語でも一応問題なく表示されます!

テンプレート販売のご案内

本記事で紹介したチャットボットUIは、商用利用も可能なテンプレートとして販売も行っています。

なぜテンプレートを販売するのか

以下のようなニーズを感じている方向けにご用意しました。

  • 「手順どおりやっても環境構築がうまくいかない…」
  • 「とにかく動くサンプルから試したい」
  • 「ブログを見て便利そうだったので、応援・寄付の意味も込めて購入したい」

開発に慣れていない方でも、最小限の手間でサクッと起動して試せる構成にしてあります。

テンプレートの活用例(カスタマイズアイデア)

このテンプレートは、個人開発や学習用途にぴったりです。
たとえば次のような改造・拡張をしてみるのもおすすめです。

  • 入力プロンプトを英語翻訳するプリプロセッサを追加
  • 生成された画像をダウンロード保存・SNS共有できるように拡張
  • 履歴管理(直近のプロンプト+画像)機能をローカルに持たせる
  • テーマカラーやデザインをカスタマイズして自分仕様に
  • サーバー側で画像をS3などに自動保存する処理を加える

プログラミング初学者向けに「課題ベース」で触ってみるのも良い練習になります。

テンプレートに含まれる内容

テンプレートには、今回紹介したプロジェクトのすべてのソースコードが含まれています。
そのため、自分でプロジェクトを一から作成・設定する必要はなく、すぐに起動可能です。

  • Next.js(App Router)による UI 実装
  • API連携済みのサーバーサイド実装
  • コメント付きのクリーンなコード構成
  • Tailwind CSS によるシンプルで改良しやすいデザイン
  • Docker 起動に対応した構成ファイル(Dockerfile, docker-compose.yml

※実行には、Node.js や Docker などの基本的な実行環境が必要です。

本記事で紹介しているテンプレートを販売中!

このチャットボットUIテンプレートは、複数のプラットフォームで販売中です。
「まずは動くものを試したい」「環境構築なしですぐに使いたい」「コード全体を確認したい」
そんな方にぴったりの構成になっています。

ほかのテンプレートも各マイページからチェックできます:

まとめ

今回は、OpenAI API × Next.js を活用して、シンプルかつ実用的な 画像生成アプリ を構築する方法をご紹介しました。

本記事のポイントは以下の通りです:

  • OpenAIのImage API を使えば、わずかなコードで画像生成機能を実現できる
  • Next.js App Router + Tailwind CSS により、UIも拡張・カスタマイズしやすい構成
  • テンプレートを使えば、環境構築やセットアップをスキップしてすぐに動作確認できる

このテンプレートは、プロトタイピングや個人開発のスターターキットとして最適です。
自分のアイデアをすぐに形にしたいときや、OpenAI API の学習・検証用途にもおすすめです。

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

情報セキュリティを勉強するために始めたブログです。
新人のため、広い心を持って見ていただけると嬉しく思います。
楽しくプログラミングを勉強するために、「Teech Lab.」もありますので、ソフトウェア開発にも興味があればぜひ覗いて見てください!

目次