📖

Next.js × LLMで10分構築!「2024年型」エッジAIアプリ入門

2024/12/15に公開

所要時間:約10分|対象レベル:初中級エンジニア

2024年時点で、Next.js 13以降がデフォルトで採用するApp RouterやEdge Runtimeを利用することで、エッジ環境で動作する高速なLLM(大規模言語モデル)連携アプリを構築することは非常に簡単になっています。
本記事では、Next.jsとOpenAI API(LLM)を組み合わせて、わずか10分程度でエッジランタイム環境から高速応答が可能なシンプルな対話型アプリを構築する方法を紹介します。

以下の手順とコードは検証済みであり、基本的な構成として再現可能な内容となっています。


ゴール

  • Next.js 13+ のApp RouterおよびEdge Runtimeを使用
  • OpenAIのLLM API(gpt-3.5-turbo)と連携したアプリを構築
  • 入力フォームからテキストを送り、LLM応答を表示
  • 開発環境はローカル、デプロイ先はVercelを想定

前提条件

※OpenAI API以外のLLM API(Hugging Face Inference APIなど)でも類似の実装が可能です。


手順

1. プロジェクト作成

Next.js公式のCLIを使用して新規プロジェクトを作成します。

npx create-next-app@latest my-llm-app

プロンプトには基本的にデフォルト回答で構いません。

生成後、my-llm-app ディレクトリに移動します。

cd my-llm-app

2. App Router構成の確認

create-next-app@latest で作成されるアプリでは、app/ディレクトリが標準で有効化されています。app/ディレクトリ内部にページやAPIルートを配置することで、App Router機能が活用できます。

ディレクトリ例:

my-llm-app/
  app/
    page.tsx
    ...

3. APIルート(Edge Runtime対応)を作成

app/api ディレクトリ下に、LLM問い合わせ用のAPIエンドポイントを作ります。
ここではapp/api/ask/route.tsというファイルを作成し、runtime='edge'でエッジ実行を指定します。

// app/api/ask/route.ts

import { NextRequest, NextResponse } from 'next/server'

export const runtime = 'edge'  // エッジランタイムを利用

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

  if (!prompt) {
    return NextResponse.json({ error: 'No prompt provided.' }, { status: 400 })
  }

  const apiKey = process.env.OPENAI_API_KEY
  if (!apiKey) {
    return NextResponse.json({ error: 'OpenAI API key not found.' }, { status: 500 })
  }

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: prompt }],
      max_tokens: 100,
      temperature: 0.7
    })
  })

  if (!response.ok) {
    const errorData = await response.json()
    return NextResponse.json({ error: 'OpenAI request failed', details: errorData }, { status: 500 })
  }

  const data = await response.json()
  const completion = data.choices?.[0]?.message?.content || 'No response'

  return NextResponse.json({ completion })
}

ポイント:

  • runtime = 'edge' により、Vercelデプロイ時にエッジ環境で実行され、低レイテンシな応答が期待できます。
  • POSTメソッドでpromptを受け取り、OpenAI APIに問い合わせ、応答をJSONで返します。

4. 環境変数設定

OpenAIのAPIキーを .env.local に設定します。
.env.local はGit管理外がデフォルトのため、APIキーの漏洩を防げます。

# .env.local
OPENAI_API_KEY=sk-xxxx...

5. フロントエンド実装 (page.tsx)

app/page.tsx に、ユーザー入力フォームと結果表示領域を持つ簡易UIを作成します。ユーザーが入力したテキストを/api/askエンドポイントにPOSTし、その結果を表示します。

// app/page.tsx
'use client'

import { useState } from 'react'

export default function HomePage() {
  const [prompt, setPrompt] = useState('')
  const [response, setResponse] = useState('')
  const [loading, setLoading] = useState(false)

  const handleAsk = async () => {
    setLoading(true)
    setResponse('')

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

    const data = await res.json()
    setLoading(false)
    if (data.error) {
      setResponse('エラー: ' + JSON.stringify(data.error))
    } else {
      setResponse(data.completion)
    }
  }

  return (
    <main style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
      <h1>Next.js × LLMで高速応答</h1>
      <div style={{ marginBottom: '1rem' }}>
        <input
          type="text"
          placeholder="質問を入力してください"
          value={prompt}
          onChange={(e) => setPrompt(e.target.value)}
          style={{ width: '300px', padding: '0.5rem' }}
        />
        <button
          onClick={handleAsk}
          style={{ marginLeft: '1rem', padding: '0.5rem' }}
        >
          {loading ? '問い合わせ中...' : '送信'}
        </button>
      </div>
      {response && (
        <div
          style={{
            whiteSpace: 'pre-wrap',
            background: '#f0f0f0',
            padding: '1rem',
            borderRadius: '4px'
          }}
        >
          <strong>応答:</strong> {response}
        </div>
      )}
    </main>
  )
}

ポイント:

  • 'use client' を付与して、React Hooksを利用するクライアントコンポーネントとして定義
  • フォーム入力値promptfetch('/api/ask') 経由でLLMに問い合わせ、応答を表示

ローカルでの実行

npm run dev

http://localhost:3000 にアクセスし、入力欄に質問を入れて「送信」をクリックするとLLMからの応答が表示されます。


デプロイ(例:Vercel)

  1. GitHubリポジトリにコードをプッシュ
  2. Vercelにログイン、"New Project" → "Import"でリポジトリを選択
  3. "Project Settings"でOPENAI_API_KEYを設定
  4. デプロイすると、エッジネットワーク上でアプリが稼働し、低レイテンシな応答が可能に

応用例

  • 別のLLMモデル利用:OpenAI以外のモデルやHugging Face APIへ切り替え
  • ストリーミング応答:LLMからのレスポンスをストリーム処理することで、段階的表示を実現
  • 認証追加:JWTやBasic認証を導入し、内部ユーザー向けアプリに応用
  • UI強化:Tailwind CSS、Chakra UIなどを用いてUI/UX向上

まとめ

本記事では、Next.js 13以降の機能(App Router、Edge Runtime)を活用し、OpenAI APIと組み合わせて、簡易なLLM応答アプリを実装する手順を紹介しました。

  • エッジ実行によりグローバル低遅延応答が可能
  • 環境変数経由で安全にAPIキー管理
  • シンプルなUIで即座にLLMとの対話体験が可能

このサンプルをベースに、より高度なLLMアプリケーションへ発展させることができます。
ぜひ、エッジAIアプリ構築の一歩として役立ててみてください。

Discussion