ブログの Next.js SSG + S3 + CloudFront に変更した

ふとしです。いつのまにやら年末です。

Nuxt.js + API Gateway + AWS Lambda から移動

前回の構成は AWS Lambda での Nuxt.js SSR でした。仕事でそういう構成のプロジェクトをやる気機会があったのでやってみた感じです。ただし仕事は Nuxt.js 2 でブログは Nuxt.js 3 beta でした。

Nuxt.js 3 からはビルドターゲットのプリセットに AWS Lambda が追加されたので、Nuxt.js 2 で行ったようなビルド調整はなくて楽でした。beta 版だったのでいろいろなあれはありましたが、3 の予習にはなって便利でした。相変わらずというか、型情報の伝播は自分の手でまごころをこめないとダメな感じでしたが、リリース版では解消されたのでしょうか。

今回の構成

記事部分 (o296.com)

表題の通り、記事部分は Next.js SSG + S3 + CloudFront です。SSG ということで、前の構成にあった AWS Lambda コールドスタートによる立ち上がりの遅さもないので快適ですね。

編集画面 (どこか)

記事編集部分は、以前の記事にも書いたとおり、フロントもバックエンドも Next.js + AWS Lambda + Lambda function URLs です。Lambda function URLs により API Gateway を介さなくても AWS Lambda 関数がネットに公開できるようになって、ドメイン付与が必要ない裏方の小機能設置にはすごく便利です。

Lambda function URLs は認証がちょっとめんどくさくて IAM Role ベースでしか行えない、つまりヘッダーにサインか何か付与しないと認証できない?みたいな?感じだと思うんですがそんな感じで不便なので、Amazon Cognito で認証を行って、それを使って AWS Lambda 内の処理で認証を行っています。

AWS Lambda の中身は Actix-Web です。Rust ですね。なぜ Rust かというと Rust を使いたかったからです。

永続化

前回の構成では S3 に直接 JSON を保存して記事の編集などをしていたのですが、Home の一覧などを作成するためのデータを用意するのがめんどくさかったりして、結局今回は RDB に変えました。RDB は良い。

デプロイ

編集画面にあるデプロイボタンを押すと GitHub Actions が起動されて、ビルドからデプロイを行います。

SSG のために微調整

SSG のデータ収集では全記事のデータを取得するのですが、そこで AWS Lambda + RDB の接続問題が発生しました。SSG では並行でリクエストを行っているようですが、各 AWS Lambda インスタンスがコネクションを握ってしまうため、すぐ上限に達してしまい接続できなくなってしまうんですね。

そこで、そもそも各ページそれぞれがページの情報を取得するのは重いってことで yarn build && yarn export 前に全記事を取得してローカルに配置して、それを使うことにしました。

接続数の問題は解消されて、一つずつリクエストするオーバーヘッドもなくなり、ビルドも早くなりました。よかったですね。

SSG ファイルアップロードの調整

aws s3 sync はディレクトリ間の同期を、アップロードの必要不必要をファイルごとに判断して行ってくれて便利です。しかし、SSG するたびに全ファイルをアップロードしてしまうようです。

スクリプトをちら読みした感じでは、最終更新日時かファイルサイズが異なっていればアップロードするようで、SSG では全ファイルが生成し直されることから、全ファイルがアップロードもしなおされてしまうようです。

  • https://github.com/aws/aws-cli/blob/2ae3943a930272e0f269a32e3f6fa17e7dee9385/awscli/customizations/s3/syncstrategy/base.py#L228-L238
    def determine_should_sync(self, src_file, dest_file):
        same_size = self.compare_size(src_file, dest_file)
        same_last_modified_time = self.compare_time(src_file, dest_file)
        should_sync = (not same_size) or (not same_last_modified_time)
        if should_sync:
            LOG.debug(
                "syncing: %s -> %s, size: %s -> %s, modified time: %s -> %s",
                src_file.src, src_file.dest,
                src_file.size, dest_file.size,
                src_file.last_update, dest_file.last_update)
        return should_sync

仕方がないのでアップロード用の S3 key を key、ファイルの md5 を value としたマップを作成して永続化しておき、次回アップロード時に差異があればアップロードして md5 マップも更新するという処理を入れて、必要なファイルだけをアップロードする措置を入れました。

md5 計算とアップロードを並列で行うのでこの処理も Rust で書きましたが、最近導入した Copilot さくさく書けていいですね。

(md5 のファイルがちょっとでかいので、もう少し小さくしたい)

まとめ

Next.js の素振りと SSG を試してみたくて今回の構成となりましたが、ページ数がそれなりにあると SSG がちょい重い気がしますね。たまにしか日記を書かないので別に構いませんが。

来年はもっと記事を書けると良いですね。