3年前、趣味で開発するウェブアプリ向けの安価なAWSアーキテクチャについて記事を書きました。当時流行りの話題だった記憶です。
趣味Webサービスをサーバーレスで作る ― 格安編 - maybe daily dev notes
最近はAWSにも新たに色々なサービスが出てきて、以前とは一味違う構成を取れるようになっています。この記事では、アップデートされた格安かつスケーラブルなウェブアプリ向けAWSアーキテクチャを紹介します。
本記事で紹介するアーキテクチャのリファレンス実装は、以前と同じリポジトリに公開しています。
主な機能は下記です:
それではDive deepしていきましょう。
以下に上記アーキテクチャの要点を説明します。
CloudFront + Lambda fURL + Lambda Web Adapterの構成で、Next.js on AWS Lambdaを実現しています。この構成の詳細は以下の資料もご覧ください。
この構成であれば、Next.js App Routerでは重要なレスポンスストリーミングの要件も達成できます。
別のアドバンスドな構成としてcdklabs/cdk-nextjsという方法もあるのですが、今回は見送っています。今の構成でも、CloudFrontのキャッシュを活用すればLambda上のNext.jsだけでも効率的に動作するように思うので、複雑性に対して得られるメリットが少ないかなと感じています。
今回は以下のライブラリも使い、クライアントからサーバー・データベースまでの型安全性が実現されています:
以前、Page Routerの時代はtRPCを使ったサンプルを作ったこともあったのですが、App RouterになってE2E型安全の実現がさらに楽になりました。ページのレンダリング時には直接DBを叩くような書き方ができますし、ミューテーションの処理もServer Actionで容易です。クライアントからのクエリだけはtRPCのほうが扱いやすかった印象ですが、Server Actionを使っても可能ではあります (クライアントのJSから実行・結果の取得ができるため)。
また、Server Actionを素で使うと、認証認可といった処理が煩雑になりがちです。こうした処理をミドルウェア的に簡単に挿入するため、next-safe-actionを利用しています。認証認可処理が実装されたAction Clientを作成し、それを使ってServer Actionを定義するだけです。tRPCのprocedureと似た書き心地ですね。
next-safe-actionは、React Hook Formと容易に統合できる点も魅力です。adapter-react-hook-formというライブラリを使えば簡単です(コード例。)フォームとServer Actionの統合方法としてはConformも人気ですが、個人的には慣れ親しんだReact Hook Formの体験を保ちたいので、こちらの方法を好んでいます。
サーバーレスサービスを主に構成されるため、リクエストが増えても自動でスケールアウトしますし、またアイドル時のコストはゼロになります。(ただしVPCを使うため、NATのみ例外。後述。)
またデータベースについても、Aurora Serverless v2が登場したことで、アイドル時のコストをゼロにできるようになりました。以前はAWSでScale to zeroするDBといえばDynamoDBだったのですが、RDBMSでこれができるようになったのは大きいです。やはり開発体験としてはNoSQLより慣れていて円滑なことが多いため。
2024年末にGAしたAppSync Eventsにより、リアルタイム通知の実装が非常に手軽になりました。これは例えば、クライアントへのジョブ完了通知などに利用できます。
従来はAPI Gateway WebSockets APIなどを利用していた部分かと思いますが、AppSync EventsではコネクションIDの管理が不要になる他、ブロードキャストの実装も容易です。
個人的には、API Gateway WebSocketをこの用途で使うと実装が煩雑になるため、これまではリアルタイム通知でなくポーリングによる実装をすることが多かったです。しかしながら、AppSync Eventsでは多くの面倒な部分をサービス(とAmplify library)側で隠蔽してくれるため、より気軽にリアルタイム通知要件を実装できるようになりました。
使い所は意外と多いので、ぜひ考えてみてください。
従来のSPAではCognitoログインを統合する場合、Amplify UIコンポーネントを使うのが一般的でした。しかしながら、こちらはクライアントコンポーネントのためSSRと相性が良くない上、トークンをhttp-onlyなCookieに保存することも困難でした。
現在はこの状況が大きく改善されており、App RouterでCognitoをうまく扱うためのライブラリAmplify Next.js adapterが提供されています。これはUIコンポーネントでなく、Cognito Managed Loginを活用してログイン機能を提供します。また、Cookieを経由してサーバーサイドでトークンやユーザー情報を取得するのも容易です。
Amplify Next.js adapterとnext-safe-actionを組み合わせることで、Server ActionをCognitoの認証で保護することも簡単にできます (コード例)。
ウェブアプリでは、しばしばジョブのスケジュール実行機能 (毎日朝4時に集計実行など) が必要になりがちです。今回はEventBridge Schedulerを使っています。
EventBridge Schedulerは従来のEventBridgeルールと比べて、cronのタイムゾーンが指定できたりタイムウィンドウを指定できたりと、上位互換のような存在です。
SchedulerのCDK L2が最近GAしたので、スケジュールもCDKで便利に一元管理できます。コード例
上記のシステムを、AWS CDKのコマンド一発でデプロイできるようにしています。この性質は初期構築を楽にするだけでなく、環境複製を容易にする意味でも重要です。
これを実現する上での難点としては、Next.jsのビルド時に、Cognito User Pool IDなどデプロイ時に決定される値が必要になることです。つまり、初回デプロイ前にNext.jsをビルドできないため、デプロイ手順が煩雑になりえます。
この問題は、デプロイ時にコンテナイメージをビルドできるコンストラクト(ContainerImageBuild
)を使うことで解消しています。以前紹介したdeploy-time-buildを改良したものです。
別解としてはnext-runtime-envというライブラリを使うことで、ランタイムの環境変数を静的ファイルに注入することができます。しかし、こちらは環境変数周りの扱いが完全には透過的でなく、特殊な実装が必要になる (コード例) ため、今回は避けています。
コストのブレークダウン表はREADMEにまとめてあります。100ユーザーで月額8.5ドルという見積もりです。もちろんこれはざっくりした概算で、実際の値はユースケース次第となります。
また、上記は無料枠を含めていません。無料枠をフル活用できる場合は、Cognito, Lambda, NAT Instanceなど多くのサービスが無料範囲に収まるため、月額5ドル未満となるでしょう。
良い話ばかりだとアレなので、何点か既知の欠点を挙げておきます:
Aurora Serverless v2においてもVPCは必要のため、Lambdaのネットワーク疎通のためにNAT Gatewayやそれに類する機能が必要となります。NAT Gatewayは小規模利用には少々コストが目立ちがちです。
小規模なユースケースではNAT Gatewayの代わりにNATインスタンスを使うことで、毎月のコストを3USD (t4g.nano)に抑えることができます。無料枠に含まれるインスタンスを使うことも可能でしょう。
あるいはそもそもVPCなしで利用可能なAurora DSQLも存在します。2025/05現在ではプレビュー状態のため今回は利用を見送っていますが、GAされれば良い代替手段となるはずです。
本アーキテクチャでは、Lambda, Auroraのコールドスタートの影響を受けます。アイドル状態からアクセスしたとき、およそ以下の待ち時間が生じます:
頻繁なアクセスのあるアプリであれば深刻な問題になることは少ないですが、アクセス頻度の少ないアプリ (5分に1回未満など) の場合、アクセスのたびにコールドスタートが生じてユーザー体験が悪化するリスクがあります。特に今回はSPAなどとは違いS3から静的ファイルを配信するわけでもないため、Lambdaが起動するまでは一切ページが描画されない点も、ユーザーに不安をもたらすポイントです。
こうしたコールドスタート問題の緩和策は、今回は以下が考えられます:
ある程度のアクセス頻度があれば問題になることは少ないと思われますが、考慮点として挙げました。
ウェブアプリへのリクエスト数が増えLambdaの利用量が増えてくると、コスト効率が悪化する場合が考えられます。Lambdaはリクエストごとに計算リソースが分離しており、I/Oバウンドな処理ではリソースが遊びがちのためです (もちろん良し悪しはあります)。
このため、利用量が大きくなると、ECSなどに移行することが必要になるかもしれません。とはいえ移行自体はそれほど困難ではないはずです。アプリ自体はNext.jsをそのまま利用でき、またステートレスな部分のみの移行のため。
必要な移行作業はLambda + 関数URLの部分を、ALB + ECSなどに置き換えるだけです。この場合scale to zeroしないためアイドル時のコストは増えますが、大規模な利用であればこのコストは支配的な要因にはならないでしょう。
今回は私の開発したサンプルを紹介しましたが、その他にもフルスタックなウェブアプリを構築する新しい手段はいくつかAWSから提供されています。
Amplify Gen2も良いスターティングポイントとなると思います。私はプレーンなCDKの方が好きなので上記のサンプル実装を作成しましたが、好みの問題でしょう。
また、オーストラリアの同僚が、最近NX plugin for AWSというツールを公開しました。これはTanStack RouterやtRPCで構築、あるいはPython Fast APIも選択できるなど、実装上の違いはあるものの、ウェブアプリの開発体験を高めたいという目的は同じです。
AWS PDKと同じメンバーが作っており、PDKでの学びが活かされてるそうです。日本語ドキュメントも用意されているので、ぜひお試しください:クイックスタートガイド | @aws/nx-plugin
最新のAWSサービス群を活用して、フルスタックなウェブアプリを構築するアーキテクチャを紹介しました。
個人的には今のところかなり開発体験が良いので、ウェブアプリはしばらくこの構成で作ろうと思います。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。