7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【完全テンプレ】Next.js + OpenAI Realtime APIで作るリアルタイム音声チャット(2025年最新)

Last updated at Posted at 2025-03-03

この記事でわかること

✅ Next.js × OpenAI Realtime APIで音声チャットを最短構築
✅ OpenAIのEphemeral Keyを使った最新構成
✅ WebRTCを使ったリアルタイム音声ストリーミングの実装

こんな人にオススメ

  • ChatGPTを音声で使えるWebアプリを作りたい
  • OpenAI Realtime APIの最新ベストプラクティスを知りたい
  • Next.js x OpenAI x WebRTCの実践コードが欲しい

GitHub

環境構築

1 Next.jsプロジェクトを作成します。

npx create-next-app --example with-docker {your project name}

2 GitHub でリポジトリを作成し、ローカルの内容をPushします。(任意)

cd {your project name}
git remote add origin https://github.com/{your git account id}/{your git repository url}
git branch -M main
git push -u origin main

3 Tailwind-CSS をインストールします。(任意)

  • Tailwind CSSをインストールする
npm install tailwindcss @tailwindcss/postcss postcss
  • PostCSS設定にTailwindを追加する
postcss.config.mjs
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  }
}
  • Tailwind CSSをインポートする
globals.css
@import "tailwindcss";

4 .env.local にAPIキー設定

.env.local
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

リソースの配置

/pages/api/openai-realtime/init.ts

OpenAIにエフェメラルキーをリクエストするAPI。

import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'GET') {
    return res.status(405).json({ error: 'Method Not Allowed' });
  }

  const apiKey = process.env.OPENAI_API_KEY;
  if (!apiKey) {
    return res.status(500).json({ error: 'API Key not found' });
  }

  const response = await fetch("https://api.openai.com/v1/realtime/sessions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4o-realtime-preview-2024-12-17",
      voice: "verse",
    }),
  });

  if (!response.ok) {
    const errorText = await response.text();
    console.error('Failed to create session:', errorText);
    return res.status(response.status).json({ error: 'Failed to create session', details: errorText });
  }

  const data = await response.json();
  res.status(200).json({ ephemeralKey: data.client_secret.value });
}

/app/pages/openai-realtime.tsx

音声チャットのUIとWebRTC接続処理。

import { useState, useRef } from 'react';

export default function Chat() {
  const [isChatting, setIsChatting] = useState(false);
  const peerConnection = useRef<RTCPeerConnection | null>(null);
  const dataChannelRef = useRef<RTCDataChannel | null>(null);

  const startChat = async () => {
    const res = await fetch('/api/openai-realtime/init');
    const { ephemeralKey } = await res.json();

    const pc = new RTCPeerConnection();

    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    stream.getTracks().forEach(track => pc.addTrack(track, stream));

    const audioEl = document.createElement('audio');
    audioEl.autoplay = true;
    pc.ontrack = (e) => audioEl.srcObject = e.streams[0];

    const dc = pc.createDataChannel('oai-events');
    dataChannelRef.current = dc;

    dc.onmessage = () => {};

    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);

    const sdpResponse = await fetch(`https://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${ephemeralKey}`,
        'Content-Type': 'application/sdp',
      },
      body: offer.sdp,
    });

    const sdpText = await sdpResponse.text();
    const answer: RTCSessionDescriptionInit = {
      type: 'answer',
      sdp: sdpText,
    };
    await pc.setRemoteDescription(answer);

    peerConnection.current = pc;
    setIsChatting(true);
  };

  const stopChat = () => {
    peerConnection.current?.close();
    dataChannelRef.current?.close();
    setIsChatting(false);
  };

  return (
    <div className="p-4">
      <h1 className="text-xl font-bold">リアルタイム音声チャット</h1>

      <div className="mt-4">
        {isChatting ? (
          <button onClick={stopChat} className="px-4 py-2 bg-red-500 text-white rounded">
            チャット終了
          </button>
        ) : (
          <button onClick={startChat} className="px-4 py-2 bg-blue-500 text-white rounded">
            チャット開始
          </button>
        )}
      </div>
    </div>
  );
}

動作確認

開発サーバー起動

npm run dev

ブラウザでアクセス

http://localhost:3000/openai-realtime

完成!! お疲れ様です

✅ Next.jsで超シンプルに音声チャット
✅ OpenAI Realtime APIの正式対応構成
✅ Ephemeral Keyによる安全設計
✅ WebRTCでリアルタイム処理

さらにこんなカスタマイズもオススメ!

  • 会話履歴を保存して「チャットログ機能」追加
  • スマホ対応デザイン(録音中アイコンとか)
  • サーバー側で音声保存してAI音声分析

そのままデプロイしたい方は!

こちらの記事で、Cloud Runを使って秒速デプロイする方法をご紹介しています!
https://qiita.com/dev-cat/items/0de2ca8168684fc21a69

🔥 最先端のリアルタイムAI体験を、Next.jsでサクッと実現しよう!

⭐️ この記事が役に立ったら「いいね」&「ブクマ」よろしくお願いします!

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?