Topics1:Next.jsプロジェクトにserverless-nextjs+AWS CDKを追加する

ここからは、(AWS CDKとNext.jsとStripeの)Topicsについてバッとしゃべっていこうと思います。まずはセットアップ方法です。そこからStripeなどのAPIキーの管理方法、設定のカスタマイズについて2種類ほど紹介していこうと思います。

(スライドを示して)セットアップ方法です。Next.jsとAWS CDK、どちらもcreateもしくはinitコマンドを設定して、アプリをプロジェクトのディレクトリごと作成する仕組みです。

どちらもすでにファイルが設置されていて、Gitで管理されているディレクトリの中に展開できないものなので、Next.jsでcreate-next-appでセットアップした後か、cdk initでCDKのセットアップをした後に、もう片方のリソースを持ってくる必要があります。

(スライドを示して)右側にコマンドを書いていますが、このあたりはハッシュタグ「#jawsug」「#cdkconf」でスライドを公開しているので、読み切れなかった方はそれぞれのPCで見てください。個人的な感触としては、CDKのほうがファイル数が少ないので、後から手動で追加しやすいかなと思います。

CDKとNext.jsです。Next.jsのディレクトリの中にCDKのディレクトリを入れて、その中でpackage.jsonを配置しようとして、CDK側がNext.jsのリソースにアクセスできないとエラーを出してきたりするので、モノレポにする必要があるかと思っています。

(スライドを示して)もし1つのpackage.jsonの中で両方管理したいと思っている方は、右側のサンプルのようにライブラリのインストールなどはまとめて行い、CDK関連のリソースはCDKディレクトリを切って、binファイルとstackファイルのそれぞれを用意してやるようなイメージがよいかなと思います。

Serverless Next.jsの特徴は、Next.js側のコードをほぼ触らないので、CDKをやめて「App Runnerに行こうとかVercelに戻ろう」となった場合、もしくはAmplifyなどに移りたい場合は、CDKのスタックを消してCDKのディレクトリを消せばそれで終わりという、持ち運びのしやすさなどもかなり特徴かなと思っています。

それから、「手動で紹介しているやり方は面倒くさいな」という方は、cdk initした後、ファイルだけ持ってくるやり方ももちろんできるので、この後の変更内容などを見ながら参考にしてもらえればと思います。

(スライドを示して)変更内容です。まずtsconfigを分けています。CompilerOptionsのmoduleの部分で、Next.jsとCDKでそれぞれ設定してほしいものが別々になっているので、Next.jsのものをCDKに使おうとする、またはその逆にしようとすると、ビルドにエラーが出ます。そのため、分けてやる必要があります。

分けた後、今度はcdk.jsonの中に実行するコマンドが書かれているのですが、このts-nodeにCDK用のtsconfigを読ませてやる必要があるので、--projectで、tsconfig.cdk.jsonを渡します。

あとはbinファイルのパス、ファイル名が変わっている場合は、それらも変更する必要があります。

CDKの定義は先ほど紹介したものとほぼ同じです。

あと、これは今回の例じゃなくても必要ですが、binファイルで、Serverless Next.jsが用意しているBuilderというクラスを使ってCDKのスタック、Appをラップする必要があります。

これは何をしているかというと、CDKでビルドやデプロイなどをする前に、Next.jsのアプリをビルドしてAWSにデプロイするためのビルドのアーティファクトを、Next.js側で先に内部側で生成しています。

これをやらないと、Next.jsでビルドをローカルでやらないとできないなどの不都合が出てくるので、忘れずに設定するようにしてください。

(スライドを示して)設定した後、ローカルでアプリを試したい場合は、Next.js側のコマンドでNext.jsをローカルで動かせばOKですし、AWSにデプロイしたくなったらCDKコマンドでデプロイなどをすれば、それで使えるようになります。

Topics2:Secret Managerを利用して、安全にAPIキーを利用する

続いて、Secrets Managerの話です。Serverless Next.jsで、なおかつStripeなどの外部のSaaSを使う場合の話になりますが、特にStripeに関しては、フロントエンド用の公開可能キーとシークレットAPIキーという2つがデフォルトで用意されています。シークレットAPIキーは、サーバー側で利用するキーです。

これは顧客情報やサブスクリプションのデータや、(漏洩などしたら)かなりまずい情報にもアクセスできるキーなので、Gitでコミットすることはもちろんのこと、AWSのシークレットと同じように、かなり厳重に扱う必要があります。

公開可能キーは名前のごとくフロントエンドで使われる前提になっていて、カードのトークン化をしたらそのトークンが自分のアカウントに送られるだけなので、持っていかれることもなく、改ざんされることもないので、コミットしてもいいです。

テストと本番の切り分けの時に必要になるので、コミットではなくても、envファイルなどにホストしてもいいかなとは思います。

もし、厳重に管理されたい方は、Stripeはカスタマイズ可能なシークレットAPIキー相当である、「どのリソースにアクセスできて、これにはアクセスさせない」という設定ができ、ユーザーごとに発行できる制限付きキーもあるので、こちらを使うのもよいかと思います。(キーは)3種類ありますが、特にシークレットAPIキーは、最低限AWSを利用している場合でも安全に管理する必要があります。

あとはServerless Next.jsを使った場合、デプロイ先のランタイムは実行環境がLambda@Edgeになってしまうので、そもそも環境変数で埋め込めません。

(スライドを示して)そのため、原則としておすすめするのはSecrets Managerを使うことです。ただ、こちらはAPI呼び出しに対する課金になるので、もしかするとビジネスの規模によっては、ローカルでもバンバン叩かれ始めるとしんどいと思われる方もいるかもしれません。

コスト面が厳しい場合は代替案として、先ほど紹介した制限付きのキーで万が一漏れても被害が最小限になるようにした上で、Secrets ManagerのParameter StoreでSecure Stringを使ってホストするやり方もあるかなと思います。

コードベタ打ちであったり、なにかしらの環境変数で開発者それぞれのPCにAPIキーをばらまくやり方よりは、Secrets ManagerかSystems Managerを使ってやるほうがよいかなと思います。もちろんSystems Managerでも使えますが、安全性、監査を考えるとSecrets Managerのほうがよいかと思います。

(スライドを示して)Secrets Managerへのアクセスを設定して行う場合は、CDKでPolicyStatementを追加する必要があります。PolicyStatementの書き方は通常どおりのCDKの書き方でできますが、NextJSLambdaEdgeでできるものとして、プロパティとして複数のLambda@Edgeが登録されています。

Secrets ManagerにアクセスしたいLambda@Edgeに対して、それぞれaddToRolePolicyを設定する必要があるので、もし設定したのにエラーが出ている場合は、ここに載っているコード以外のLambda@EdgeでaddToRolePolicyを設定する必要がある可能性もあるので、そこは注意してください。

あとは、呼び出し側もSecretsManagerのインスタンスを立てて、getSecretValueとしてやるだけです。

Topics3:CloudFrontのキャッシュ設定をカスタマイズする

(スライドを示して)CloudFrontのキャッシュ設定カスタマイズも、先ほどと同じように戻り値の中にnextLambdaCachePolicyがあるので、ここに対してCDKでCachePolicyを書くと、独自のCachePolicyを上書きできます。

これ以外にも、いろいろなプロパティがこの戻り値の中に入っているので、TypeScriptもしくはJavaの型付き言語であれば型の定義をかけることで、どういうものがあるか、どういうものが上書きできるかを追いかけられると思います。

ただ、一部だけ上書きしたいとなると、チェーンがけっこう長くなったり、もしくはreadOnlyが付いていると書けなくなるかもしれないので、使う場合は全部手で書くぐらいのつもりで始めたほうが「やはりできませんでした」という事態にはなりにくいかなと思います。

型定義を読むのがしんどい方は、cdk synthでCloudFormationのスタックをYAMLで吐き出してそちらを読むという手や、デプロイして設定を見るやり方もできるので、試してみてください。

もちろんAmplifyでもけっこうできることはあるので、このあたりも比較対象、比較検討の項目に挙げるといいかなとは思います。

Topics4:独自ドメイン設定のために、ACMを設定する

最後にACM(AWS Certificate Manager)の設定です。先ほどのは戻り値を上書きするやり方をしていましたが、ドメインに関する設定や、内部でログをするのか、キャッシュヘッダーについてなど、ある程度Serverless Next.jsをリリースしているチームが想定しているものは、Constructの第3引数で上書きできます。

ただ、withLoggingやwhiteListedHeadersでかなりシンプルに、書きやすいようになっている半面、CloudFormationがどう変わるのかは、コードを追ってみるか、synthもしくはdeployしてみないとわからない部分にはなってきます。

実際に導入する前は、テスト環境などを用意して、どういうふうに変わるのかを一度検証したほうが安全かなと思います。

Serverless Next.jsを使ってAWSのリソースをガンガン使う

というところで、まとめです。CDKを使うとNext.jsのアプリとAWSがかなり連携しやすくなるし、App Runner、Amplifyと、加えてCDKという選択肢も入ることで、かなり柔軟性もできるかなと思います。ただ、Serverless Next.jsでCDKを使った場合は、Lambda@Edgeがかなりの数登場してくるので、ここについてだけは要注意です。

だから、CDKを使ってAWSをフルフルに使いたい場合はServerless Next.jsですが、「ちょっとそういうのはうちのチームに厳しいな」という場合は、Amplifyを検討したり、そもそもAWSを使わないならVercelを使う選択肢もあります。

ただ、AWSに関しては、始めることもやめることもしやすいのはServerless Next.jsだと思うので、このあたりを使ってAWSのリソースをガンガン使う開発をやっていくといいのではないかなと思います。以上です。ありがとうございました。

吉田真吾氏(以下、吉田):ありがとうございました。

新居田晃史氏(以下、新居田):ありがとうございました。

Lambda@Edgeに載せた場合の性能

吉田:Lambda@Edgeに載せた時、ある程度スケーラビリティがあったり、いろいろなところからアクセスする時に、性能的な面はどうなんですか?

岡本:使っている印象としては、「ああ、Lambda@Edge使っているな」みたいな感じはないというか、「Next.jsのアプリだな」という感じなので、そんなに気にならないかとは思います。

吉田:逆に言うと、ギュンギュンに速くなる感覚も別にそんなにない感じですか?

岡本:個人で使っているだけなので、そう(感じるの)かもしれないですね。ただ、理論上は「Edge LocationでNext.jsが動く」という考え方で、東京に置いたApp RunnerでNext.js動かしているところに対して、USからアクセスすることを考えると、Lambda@Edgeのほうが早いかなとは思います。

吉田:なるほど。100ミリセカンド、200ミリセカンドみたいなターンアラウンドタイムを気にするところがあればというところはありますよね。

岡本:そうですね。

Next.jsとCDKを同時に使うようにした理由

吉田:なるほど。新居田さん、質問は来ていますか?

新居田:そうですね。「Next.jsとCDKは同時に使いたい」という背景を聞き逃してしまった人もいるようなので、もう1回そのあたりのモチベート的なところを聞かせてもらえるといいかなと思います。

岡本:そうですね。もう身も蓋もありませんが、CDKを使いたかった時に手元にNext.jsがあったということです。個人でWebサイトを作ったりしているのですが、確かAmplifyだとキャッシュの設定かなにかでやりたいことができなくて、ほかの方法を探した時にServerless Next.jsが出てきて、調べてみるとCDKでできるから、「じゃあCDKを試してみよう」と始めたという背景です。

新居田:ありがとうございます。やれる手段を探した時に行きついたという感じですね。

岡本:そうですね。あとは、個人的には全部コードで管理したい派なので、Amplify Consoleはあまり使いたくなかったということもあります。

吉田:なるほど。ちなみにNext.jsのいろいろな機能をフルで使うとしたら、先ほどのLambda@Edgeに載せる機能的なLambdaは4種類で十分ですか?

岡本:自分がコードを読んだ範囲では、4つで行けているみたいです。できるだけNext.jsをそのままで動かしたいという思いがあるのかなと個人的には思っています。

ただ、画像の最適化や、アプリ側でやるキャッシュの再生成みたいなバッチ的なジョブに関しては裏側でできないので、それぞれ専用のLambdaを作ります。APIに関しても、たぶん分けたほうがいい理由がなにかしらあって分けたんだと思います。

分かれると逆に管理が面倒くさいし、あまり増えることはないのではないかなと思います。

吉田:了解です。藤井さんはいかがですか?

藤井貴昭氏(以下、藤井):私も「ふんふん」と聞かせていただきました。ありがとうございます。

岡本:いえいえ。しゃべりたい内容をひたすらしゃべってしまって(笑)。

(一同笑)

キーの扱う上での注意点

吉田:(これまでの話と)関係ないけれど、Stripeの各種機能を操作可能なAPIキーと、アカウント側のAPIキーと合わせて使わないと復号できない、合わせ鍵のようなAPIキーの考え方というか、使い分けにはけっこう需要がありますよね。

岡本:ありますね。

吉田:メチャクチャいいよね。そういう実装になっていると、絶対にフロントから使いやすいよね。

岡本:そうなんですよね。ただあれは、たまにごっちゃにされるので、サンプルの書き方はムチャクチャ気を使います。

吉田:間違ってもサーバー上のデータ全部を開けられるキーを書いてはダメと。

岡本:一応、StripeのAPI側もシークレットAPIキーを欲しいところに公開可能キーを入れる、もしくは逆をしたら、エラーメッセージで「お前、キー違うぞ」とは言ってきます。

ですが、環境変数に設定する時の名前を間違えたり、間違えてコミットしてしまったら、IAMと一緒で気づいた時に再生成すればいいです。

吉田:桁数とかも一緒?

岡本:長さは数えたことはないのですが、だいたい同じですね。ただプレフィックスが違います。

吉田:なるほど。パッと見て、「危なっ!」みたいなのはわりとありますか?

岡本:公開可能とシークレット、あと本番とテストとあるので、コピーして貼り付ける時に初めの5文字は絶対に確認します。

吉田:Stripeを使う人はCDKは関係ないですが、そういうところも注意しましょう(笑)。

岡本:本当はこの話も含めて、もっといろいろな話もできたらいいなと思ったのですが、今回はNext.jsに寄り過ぎてしまいました。

吉田:また機会を作りましょう。

岡本:ぜひよろしくお願いします。

吉田:今日はありがとうございました。

岡本:ありがとうございました。