0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Vercel AI SDK - AWS Lambda】文字がパラパラ出るストリーミングチャットを最速実装する

Last updated at Posted at 2026-02-05

はじめに

今回は、Next.js(ローカル環境)からAWS Lambdaを直接叩き、Amazon Bedrock(Claude 3)と会話するチャットアプリを作ります。API Gatewayは使いません。複雑なインフラ構築もなし。

「文字がパラパラと流れてくる」リッチなUI体験を、わずかなコード量で実装します。

この記事を読み終える頃には、あなたのローカル環境で「動くAIチャット」が完成しているはずです。それではいきましょう!

この記事を読むと、以下のことができるようになります。

  • 文字がパラパラと流れてくるリッチなUIを作ることができます。
  • サーバー構築やデプロイなしで、ローカル環境で動くAIチャットシステムの基礎が理解できる
  • AWS Lambda、Vercel AI SDKの実装方法がわかる

今回の構成

以下の構成で「疎通」と「ストリーミング」の成功体験を目指します。

  • Frontend: Next.js (Local) + Vercel AI SDK
  • Backend: AWS Lambda (Node.js 22.x) + Function URL
  • AI Model: Amazon Bedrock (Claude 3 Haiku)

環境・前提条件

ハンズオンを進めるにあたり、以下が完了していることを前提とします。

  • AWSのアカウントの作成: 公式リンク
  • Node.jsのインストール: 公式リンク
    • インストールすると npm も自動的に利用可能になります。

検証環境

今回の検証に使用した環境は以下の通りです。

項目 バージョン 備考
OS macOS Sequoia V15.3.2 -
Runtime Node.js v22.22.0 LTS推奨
Framework Next.js v16.x.x App Router 使用
Frontend Lib ai v6.0.69 Vercel AI SDK コア (ストリーム処理等)
同上 @ai-sdk/react v3.0.72 React Hooks (useChat 等)
Backend Lib @ai-sdk/amazon-bedrock v4.0.48 Bedrock用プロバイダー
同上 @aws-sdk/client-bedrock-runtime v3.983.0 AWS公式クライアント (プロバイダーが内部で使用)
AI Model Claude 3 Haiku 東京リージョン (ap-northeast-1)

ライブラリのバージョンについて(重要)
Vercel AI SDK や AWS SDK はアップデート頻度が高く、**破壊的変更(メソッド名の変更や非推奨化)**が入ることがあります。

本記事のコードは、上記の表に記載されたバージョンで動作確認を行っています。
もし npm install した最新版でエラーが出る場合は、以下のようにバージョンを指定してインストールし直すことを推奨します。
最も推奨するのは公式ドキュメントを参照し、実装方法を確認してください。

# バージョンを固定してインストールする例
npm install ai@6.0.69 @ai-sdk/react@3.0.72 @ai-sdk/amazon-bedrock@4.0.48

1. Amazon Bedrockとは

簡単に言うと、サーバー管理不要で、ClaudeやAmazon Titanなどの高性能モデルをAPI経由で使えるサービスです。様々なこと(Knowledge Baseの構築など)が可能ですが、今回は下記を使用します。

Amazon Bedrock の料金

Bedrockは「使った分だけ(オンデマンド)」の課金です。 今回選定している Claude 3 Haiku は、「速くて安い」のが特徴です。

※料金はリージョンや時期により変動しますので、公式をご確認ください。

モデル名 項目 料金 (USD) 料金イメージ (1ドル=150円)
Claude 3 Haiku 入力 (Input) $0.25 / 100万トークン 約37.5円 / 文庫本約10冊分
同上 出力 (Output) $1.25 / 100万トークン 約187.5円 / 文庫本約10冊分

公式: Amazon Bedrock 料金


手順1. Bedrockを使用可能にする

AWSの管理画面で「Bedrockのモデルを使う許可」を申請する必要があります。これを忘れると、プログラム実行時にエラーになります。

💡 なぜ申請が必要なの? Bedrockは使った分だけ料金がかかる従量課金制です。意図しない高額なモデルをうっかり使わないよう、AWS側で初期状態はすべて「無効(OFF)」になっています。使うものだけをONにする、安全設計なのです。

  1. AWSコンソールにログイン:
    AWS Management Console にアクセスします。

  2. リージョンの確認:
    画面右上のリージョン選択メニューで使用したいRegionを選択してください。今回は、「アジアパシフィック(東京)ap-northeast-1」 を使用します。
    スクリーンショット 2026-02-05 8.24.57.png

  3. この後は、この方が丁寧に解説してくださっているので、参考にしてください。

手順2: Lambda関数の作成

まずは、AIからの返答を生成するLambda関数を作成します。 ここでの技術的な挑戦は 「Response Streaming」 です。従来のLambdaは処理が終わるまでレスポンスを返せませんでしたが、これを使うことで生成された文字を逐次フロントエンドへ送ることができます。

  1. AWSコンソール画面をブラウザで表示し、検索窓に「Lambda」を入力し、Lambda画面へ
  2. 右上の「関数を作成」をクリック
  3. 関数名: bedrock-chat-stream(任意)
  4. ランタイム: Node.js 22.x (最新のLTS推奨だが、今回は22.xを使用)
  5. アーキテクチャ: x86_64 または arm64
    • x86_64より安価で高速なため、こちらを使用します。

    💡 開発時のポイント 今回は影響しませんが、「開発しているPCのCPUと、Lambdaのアーキテクチャを合わせる」のがトラブル回避の鉄則です。 ライブラリによっては、PC(Intel等)とLambda(arm64等)でCPUの種類が食い違っていると、動かない場合があるためです。

  1. 「関数を作成」をクリック
  2. 作成完了後、作成したLambda関数のダッシュボードに遷移するので、「設定」タブをクリック
    スクリーンショット 2026-02-05 11.47.33.png
  3. 右上の「編集」ボタンをクリックし、「タイムアウト」を「1分 00秒」にし、「保存」をクリック。
    ※生成AIのチャット生成に時間がかかるため。今回は簡易実装のため1分だが、実際に実装する場合は3〜5分の方が良い場合もあるので、用途によって使い分けてください。
    スクリーンショット 2026-02-05 14.59.38.png
  4. 左下の「アクセス権限」をクリックし、「ロール名」をクリック
    スクリーンショット 2026-02-05 15.03.04.png
    スクリーンショット 2026-02-05 11.49.22.png
  5. I AMロール編集画面に移動するので「アクセス許可を追加」をクリックし「インラインポリシーを作成」をクリック
    スクリーンショット 2026-02-05 11.51.38.png
  6. JSONタブをクリックし、下記をコピペ
    {
    	"Version": "2012-10-17",
    	"Statement": [
    		{
    			"Sid": "BedrockAccess",
    			"Effect": "Allow",
    			"Action": [
    				"bedrock:InvokeModel",
    				"bedrock:InvokeModelWithResponseStream"
    			],
    			"Resource": "*"
    		}
    	]
    }
    
    スクリーンショット 2026-02-05 11.56.37.png
  7. 「次へ」をクリックし、ポリシー名に「BedrockPolicy(任意)」と入力し「ポリシーの作成」をクリック。

これでLambdaがBedrockを使えるようになります!

手順3. ローカルでプログラム実装

手順3.1. 必要なパッケージのインストール

まずは、Lambdaにデプロイするためのソースコードを管理するディレクトリを作成し、プロジェクトを初期化します。
今回は**「コールドスタート対策(起動速度の向上)」**を意識し、巨大な aws-sdk 全体をインストールするのではなく、必要な client-bedrock-runtime だけをピンポイントで導入します。

  1. プロジェクトの作成と初期化
    ターミナル(Mac/Linux)またはPowerShell(Windows)を開き、以下のコマンドを順に実行してください。

    mkdir streaming-pro
    cd streaming-pro
    
    # package.json の生成(-y オプションで質問をスキップしてデフォルト設定で作成)
    npm init -y
    
  2. 最小限のAWS SDKをインストール
    続いて、Amazon Bedrockを操作するために必要なパッケージのみをインストールします。

    npm install @aws-sdk/client-bedrock-runtime
    npm install ai @ai-sdk/amazon-bedrock
    
  3. ディレクトリ構成の確認
    ここまでの作業で、ディレクトリ内は以下のようになっているはずです。

    streaming-pro/
    ├── node_modules/       # インストールされたパッケージ群
    ├── package.json        # プロジェクト設定ファイル
    ├── package-lock.json   # 依存関係のバージョン固定ファイル
    └── (ここに index.mjs を作成します)
    
  4. ソースコードの実装
    プロジェクトディレクトリ(streaming-pro)の直下に、メインの処理となる index.mjs を作成します。

    import { bedrock } from '@ai-sdk/amazon-bedrock';
    import { streamText, convertToModelMessages } from 'ai';
    
    export const handler = awslambda.streamifyResponse(async (event, responseStream, _context) => {
      let body = {};
      try {
        body = event.body ? JSON.parse(event.body) : {};
      } catch (e) {
        responseStream.write("Error: Invalid JSON");
        responseStream.end();
        return;
      }
    
      responseStream.setContentType('text/plain; charset=utf-8');
    
      const messages = body.messages || [];
    
      try {
        const result = streamText({
          model: bedrock('anthropic.claude-3-haiku-20240307-v1:0'),
          messages: await convertToModelMessages(messages),
        });
    
        for await (const chunk of result.textStream) {
          responseStream.write(chunk);
        }
      } catch (error) {
        console.error("Stream Error:", error);
        responseStream.write(`Error: ${error.message}`);
      } finally {
        responseStream.end();
      }
    });
    

手順4. Lambdaへのデプロイ(Zipアップロード)

ローカルで作成したコードを、AWS Lambdaへ反映させます。

  1. ファイルのZip化: index.mjs, node_modules, package.json をまとめてZipファイルに圧縮します。
    ※フォルダごと圧縮するのではなく、ファイルを選択して圧縮してください。
    • Mac/Linux: streaming-proディレクトリ上で、下記コマンドを実行
      zip -r deploy.zip index.mjs node_modules package.json
      
    • Windows: 上記の3ファイル(およびフォルダ)を選択して右クリック → 「Zipファイルに圧縮」
  2. AWSコンソールでアップロード
    1. Lambda関数の画面で 「コード」 タブを開きます。
    2. 右側の 「アップロード元」 → 「.zipファイル」 をクリック。
      スクリーンショット 2026-02-05 13.21.39.png
    3. 作成した deploy.zip をアップロードして 「保存」 をクリックします。
      スクリーンショット 2026-02-05 13.24.58.png

これで、ローカルのコードがLambda上で動く状態になりました。

手順5. 関数URLの発行とCORS設定(★最重要ポイント)

ここが最大のハマりどころです。API Gatewayを使わない代わりに、Lambda関数URLを発行して公開します。

  1. Lambdaコンソールの「設定」タブ -> 「関数URL」を選択。
    スクリーンショット 2026-02-05 12.21.04.png
  2. 「関数URLを作成」をクリック。
  3. 認証タイプ: 今回はテスト用なので NONE (パブリック) を選択します。
    ※本番で実装する場合、絶対にAWS_IAM を設定してください!
  4. その他の設定: ここを必ず設定してください!
    • 呼び出しモード : 「RESPONSE_STREAM」にチェック
    • オリジン間リソース共有 (CORS) を設定: チェック
      • 許可オリジン: * (またはローカル開発環境の http://localhost:3000
      • 許可ヘッダー: content-type (これがないとNext.jsからのJSON送信がブロックされます)
      • 許可メソッド: POST
        スクリーンショット 2026-02-05 12.32.23.png
  5. 「保存」をクリック

これで、https://<ランダム文字列>.lambda-url.ap-northeast-1.on.aws/ のようなURLが発行されました。これをメモしておきます。
スクリーンショット 2026-02-05 12.34.51.png


手順6: フロントエンドの実装 (Next.js)

続いて、フロントエンドです。Vercel AI SDKのおかげで、実装は驚くほどシンプルです。

ディレクトリ構成の確認
ここまでの作業で、ディレクトリ内は以下のようになっているはずです。

your-root-directory/
├── streaming-pro/  # 今までに作成したディレクトリ
|    ├── node_modules/
|    ├── deploy.zip
|    ├── package.json
|    └── index.mjs
└── my-ai-chat/  # 今から作成するディレクトリ
     ├── node_modules/
     ├── app/
     ├── package.json
     ├── package-lock.json
     └ さまざまなファイルたち

1. Next.jsプロジェクトの作成

your-root-directory上で行ってください。

npx create-next-app@latest my-ai-chat
# 設定はすべてDefault(Yes)でOK
cd my-ai-chat

2. Vercel AI SDKのインストール

npm install ai @ai-sdk/react

3. Chatコンポーネントの実装: app/page.tsx に下記をコピペしてください。

"use client";

import { useChat } from "@ai-sdk/react";
import { TextStreamChatTransport } from "ai";
import { useState } from "react";

export default function Chat() {
  const [input, setInput] = useState("");

  const { messages, sendMessage, status } = useChat({
    transport: new TextStreamChatTransport({
      api: process.env.NEXT_PUBLIC_LAMBDA_URL!,
    }),
  });

  const handleSend = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!input.trim() || status === "streaming") return;
    sendMessage({ text: input });
    setInput("");
  };

  return (
    <div className="flex flex-col w-full py-24 mx-auto h-screen bg-white px-4 text-black">
      <h1 className="text-2xl font-bold text-center mb-8">Claude 3 Chat</h1>

      <div className="flex-1 overflow-y-auto pb-24">
        {messages.map((m, index) => (
          <div
            key={m.id}
            className={`whitespace-pre-wrap mb-4 p-4 rounded-lg max-w-[80%] ${
              m.role === "user" ? "bg-blue-100 ml-auto" : "bg-gray-100 mr-auto"
            }`}
          >
            <strong>{m.role === "user" ? "User: " : "AI: "}</strong>
            {m.parts.map((part, partIndex) =>
              part.type === "text" ? <span key={partIndex}>{part.text}</span> : null
            )}
            {m.role === "assistant" && index === messages.length - 1 && status === "streaming" && (
              <span className="ml-2 text-gray-500 animate-pulse"></span>
            )}
          </div>
        ))}
      </div>

      <form onSubmit={handleSend} className="fixed bottom-0 left-0 right-0 w-full max-w-md mb-8 bg-white p-2 mx-auto">
        <div className="flex items-center gap-2">
          <input
            className="flex-1 p-2 border border-gray-300 rounded shadow-xl"
            value={input}
            placeholder="何か聞いてみて..."
            onChange={(e) => setInput(e.target.value)}
            disabled={status === "streaming"}
            autoFocus
          />
          <button
            type="submit"
            disabled={status === "streaming"}
            className="h-10 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:opacity-50"
          >
            🚀
          </button>
        </div>
      </form>
    </div>
  );
}

4. 環境変数の設定: .env.localファイルを作成

URLをコードに直書きしないよう、環境変数ファイルを作成します。 Next.jsでは、.env.local ファイルを作成し、NEXT_PUBLIC_ プレフィックスをつけることでブラウザ側からアクセス可能になります。
値は、AWSコンソールから「関数URL」をコピーして貼り付けてください。

スクリーンショット 2026-02-05 12.34.51.png

NEXT_PUBLIC_LAMBDA_URL=ここにペースト

5. 動作確認: ターミナルで以下を実行

npm run dev

http://localhost:3000 でチャットを試してみてください。Claude 3 Haikuが高速にストリーミング返答してくれば成功です!

スクリーンショット 2026-02-05 15.27.08.png

参照ドキュメント

まとめ

お疲れ様でした!
これで、あなたのローカル環境とAWS上のAIが一本の線で繋がりました。

今回作成したアプリは、見た目こそシンプルですが、裏側では Next.js、AWS Lambda、Amazon Bedrock という現代のモダン開発に欠かせない技術要素がギュッと詰まっています。

今回達成したこと

  • サーバーレスアーキテクチャの構築: API Gatewayを使わず、Function URLだけでシンプルに公開する方法を習得しました。
  • ストリーミングの実装: AIチャット特有の「文字がパラパラ流れる」体験を、Vercel AI SDKを使ってわずかなコードで実現しました。
  • コストパフォーマンス: 常時起動のサーバーを持たず、リクエストがあった時だけ課金される(しかも安価なClaude 3 Haikuを使う)お財布に優しい構成です。

次のステップ(応用編)

このアプリをベースに、さらに機能を拡張してみましょう。

  1. 認証機能をつける:
    現在は誰でもアクセスできてしまうため、AWS CognitoやClerkなどを導入して、自分だけが使えるようにセキュリティを強化してみましょう(※本番運用の際は必須です!)。
  2. 履歴を保存する:
    DynamoDBを追加して、過去のチャット履歴を保存できるようにすると、より実用的なアプリになります。
  3. 他のモデルを試す:
    Lambdaのコードにある modelId を書き換えるだけで、より賢い Claude 3.5 Sonnet や、Amazon独自の Titan モデルなどに瞬時に切り替えられます。

「動くものを作る」ことは、エンジニアリングにおいて最も強力な学習方法です。ぜひ、このチャットアプリを自分好みにカスタマイズして、さらに技術を深掘りしてみてください!

※今回作成したプロジェクトのGitHub repository: URL

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?