5
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?

LambdaWebAdapterでNext.js+Amplifyのレスポンスストリーミングとタイムアウト問題を解消した

Last updated at Posted at 2025-12-09

はじめに

はじめまして.
KDDIアジャイル開発センター株式会社の岩田です.

入社して初めてAWSに触れ,その便利さに日々感動している新卒エンジニアです.

私は入社してからAmplifyを使う機会が多かったので,「インフラをほとんど意識せずにWebアプリを公開できる」というAmplifyの開発体験にすっかりハマっていました.その上,GitHubと連携しておけばPRを作成するだけで自動でプレビュー環境を作ってくれるので,正直「Amplifyって何でもできるんじゃないか」とまで思っていました.

しかし,どんなサービスにも制約はあるもの.今回はNext.js + AIチャットアプリの開発に携わって知ったAmplifyの2つの制約と,その解決策として採用された Lambda Web Adapter について話していきます.

背景

当初のAIチャットアプリの構成は以下でした.(抜粋)

  • JSフレームワーク:Next.js
  • バックエンド:AWS Amplify
  • TypeScript/Reactライブラリ:assistant-ui
  • ...

このassistant-uiとは,ChatGPTのようなAIチャットアプリのUIを簡単に構築できるTypeScript/Reactライブラリです.

詳細は省きますが,公式のGetting Startedに書かれた手順を行うだけで,以下のようなチャットアプリが完成します.
スクリーンショット 2025-12-09 23.25.00.png

制約その1:レスポンスストリーミング

assistant-uiは公式ドキュメントでもまずNext.jsを前提にしたセットアップ手順(npx assistant-ui@latest create)が紹介されており,「Next.js上で動かすこと」を基本ユースケースとして想定しているライブラリです.
Next.jsが用意するAPIルートに以下のように書くことで,assistant-uiと連携したストリーミング対応のチャットAPIエンドポイントが完成します.

app/api/chat/route.ts
import { bedrock } from "@ai-sdk/amazon-bedrock";
import { convertToModelMessages, streamText } from "ai";

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: bedrock("anthropic.claude-3-haiku-20240307-v1:0"),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}

しかし,いざAmplifyにデプロイしてAIに質問してみると,,,
スクリーンショット 2025-12-10 2.14.42.png
スクリーンショット 2025-12-10 2.12.59.png
画像では伝わりにくいですが,AIが数秒考えた後パッと全文が返ってくるようなUXになりました.文章が長ければ長いほど,AIが全文を返してくるまでの時間が長くなります.つまり,Amplify側がレスポンス全体をバッファリングしてしまい,処理が全て完了してから一度にクライアントへ送信される挙動となっています.

これはAWS re:Postにも質問として挙がっており,AWS Amplify上にホストされているNext.jsのAPIルートではレスポンスストリーミングに現在対応していないとのことです.

制約その2:タイムアウト

AIと対話していると発生したもう一つの問題がタイムアウトです.例えば,回答に長い時間が必要な質問をした場合,

スクリーンショット 2025-12-10 2.36.51.png
スクリーンショット 2025-12-10 5.01.36.png

このように,途中で力尽きて回答をやめてしまいました.ネットワークタブを見てみると,30秒で通信が途切れていることが分かります.何度回答を生成しても30秒で通信が途切れるため,何かしらのタイムアウトが起きていることがわかります.

Amplify + Next.jsでは呼び出したサーバ処理が30秒を超えた場合,リクエストがタイムアウトエラーとなってしまうようです.開発当時はこの30秒という上限を変更することはできませんでした.公式Githubのイシューにも取り上げられており,依然未対応であることがわかります.

AmplifyからLambda Web Adapterへ

ここまでで挙げた2つの制約はどちらもAmplifyに起因していました.そこで開発中に採用されたのがLambda Web Adapterです.Lambda Web Adapterとは,普通のWebサーバとして動くアプリを格納したコンテナを,そのままLambda上で動かせるようにする拡張機能です.

通常,Lambdaはイベント駆動であり,1リクエストごとにプロセスが起動・終了します.このモデルをそのまま用いた場合,常時稼働するようなWebサーバと相性が良くありません.Lambda Web Adapterはここをうまく橋渡ししており,
fce43d6de9e961f2726debbe4924dfc8-aws-lambda-web-adapter-architecture-diagram-japanese-1400x471.266828179c7ce0d4337171389b170f1749de773b.png

  • 受け取った Lambda イベントを HTTP リクエストに変換
  • コンテナ内で起動している Next.js サーバにプロキシ
  • レスポンスを Lambda のレスポンス形式に変換して返却

これらの処理をよしなにやってくれています.

AWS SAMでNext.js + Lambda Web Adapterをデプロイ

Lambda Web Adapterを使うとしても,実際どうやってデプロイするかを考えなければなりません.今回の構成では,こちらの記事こちらのサンプルを参考にしてAWS SAMを利用してデプロイしました.

これらを元に作成したのが,以下のtemplate.yamlです.(一部抜粋)

template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: "Sample Lambda Streaming Response SAM Template using Next.js"

Globals:
  Function:
    Timeout: 60 # 全Lambda関数のタイムアウト設定(秒)

Resources:
  StreamingNextjsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./ 
      MemorySize: 512 
      PackageType: Image 
      Architectures:
        - x86_64 
      Role: !GetAtt StreamingNextjsFunctionRole.Arn 
      Environment:
        Variables:
          AWS_LAMBDA_EXEC_WRAPPER: /opt/extensions/lambda-adapter 
          NEXT_TELEMETRY_DISABLED: "1"
          NODE_ENV: production
      FunctionUrlConfig:
        AuthType: NONE
        InvokeMode: RESPONSE_STREAM # ストリーミングレスポンスモード
    Metadata:
      DockerTag: v1
      DockerContext: ./ 
      Dockerfile: Dockerfile 

まず,Next.jsアプリを含むコンテナイメージをビルドし,そのイメージをLambdaにデプロイしています.このコンテナイメージの中にNext.jsのアプリ本体Lambda Web Adapter(/opt/extensions/lambda-adapter を格納しています.(/opt/extensions/lambda-adapterはDockerfile内で定義)

次に,Lambdaはレスポンスストリーミングに対応しているため,FunctionUrlConfigInvokeModeRESPONSE_STREAMとします.こうすることで,Next.jsのAPIルートのレスポンスストリーミングをクライアントまでそのまま流せるようになります.(Bedrockを使用する場合は,適宜InvokeModelWithResponseStreamポリシーの付与が必要)

最後に,Lambda関数はタイムアウト時間を30秒以上に設定できるため,GlobalsFunction内にあるTimeoutを60秒に設定しています.必要に応じてさらに長く設定できるため,より柔軟な設計になったかと思います.

以上を行うことで,デプロイした環境ではAIからストリームレスポンスを受け取れ,長い回答も安定して出力することが確認できました.
スクリーンショット 2025-12-10 7.46.52.png

最後に

本記事では,Next.js + assistant-ui を Amplify で動かそうとした際に直面した

  • レスポンスストリーミングに非対応
  • APIルートのタイムアウト(30秒)

という 2つの「制約」について紹介し,それらを解消するために Amplify から Lambda Web Adapter + AWS SAM へ移行した経緯と,参考にさせていただいた記事・サンプルをまとめました.

Lambda Web Adapter を用いることで,

  • Next.jsのレスポンスストリーミングをそのまま活かせること
  • Lambdaのタイムアウト設定を変更することで,30秒を超える処理にも対応できること

を確認でき,AIチャットアプリとして「ストリーミングしながら最後まで回答してくれる」構成に近づけられたと思います.

一方で,今回の構成ではLambda Function URLをそのまま公開しているため,セキュリティはかなりガバガバです.Function URLの前段にCloudFrontやAPI Gatewayを置いてWAF/OACを組み合わせることで対応していきたいと考えています.

ここまでに紹介した技術や構成の多くは,社内の先輩が見つけてきた記事や検証結果です.ほとんど「先輩の肩の上」に立って書いた記事ですが,同じような課題にぶつかった方の助けになれば幸いです.

初めての技術記事で拙い部分も多かったかと思いますが,最後までお読みいただきありがとうございました.
今後の記事では,セキュリティや本番運用を意識した改善案の検証結果なども共有していければと思います.

5
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
5
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?