はじめに
Viteを用いて作成したReact×TypeScript環境をAWSのS3とCloudFrontを用いて静的ウェブサイトを公開します。説明にReactを使うだけでS3に置くのは静的ファイルなので他の環境でも同様の方法で再現可能です(記事ではReactのコードにほとんど触れません)。
何を行っているかが分かりやすいという観点から、awsコンソール上からの操作で設定していきます。また、今回最小限の設定で構築しました。お好みで設定を加えてお使いください。
開発環境のセットアップ
Viteの環境はワンラインで作ることが出来ます。
npm create vite@latest
コマンドラインに従って入力を進めると環境を作成できます。
作成した環境に入ってnpm install、npm run devを行って動作を確認してください。
ブラウザで以下のような画面を確認できたら完了です。

npm run buildを行うと、dist配下にバンドルされたファイルが出力されます。このファイルたちが今回公開するファイルとなります。
S3にファイルを設置する
awsのコンソールにログインして、s3バケットを作成します。バケット名には適当な名前、リージョンは住んでいるところから近い東京にします。

作成後はテストのためにbuild配下のファイルをアップロードしておきます。アップロード後は以下のようになります。

Cloud Frontで配信する
Cloud Frontの画面を開いてディストリビューションを作成します。オリジンドメインに先ほど作成したs3バケットを選択してください。

次にS3バケットアクセスの設定を行います。この記事ではOACを用いて設定します。

Origin access control settingsを選択後、コントロール設定を作成を押してOACを作成します。基本的には何も手を加えず作成で問題ないです(Cloud Frontの設定後にS3バケットポリシーを更新します)。

最後にデフォルトルートオブジェクトをindex.htmlに設定すれば完了です。

一番簡単な構成を目指したので今回は行いませんでしたが、カスタムドメインを使用したい場合やCloud Frontのログ、HTTPSなどの設定を行えます。
ディストリビューションの作成後に設定するS3バケットポリシーをコピーできるのでコピーしてください。

見逃してしまった場合はオリジンタブを開いて作成したオリジンを編集するページに移ると表示されるところから参照できます。

コピーしたポリシーはS3の設定で適応します。対象のバケットのページにアクセスして、アクセス許可を開きます。そこでバケットポリシーを編集できるのでペーストしてください。変更の保存ができたら完了です。
これでs3とCloud Frontを使ったホストを行う設定の完了です。Cloud Frontのページにディストリビューションドメイン名が表示されているのでそこにアクセスしてください。

アクセスすると、最初にnpm run devで見たページが見れるはずです。

Github ActionsでCDを組む
Github Actionでmain branchにマージされたらマージした内容がサイトに反映されるようにします。
まずGithubのリポジトリを作りましょう。入力する必要があるのは名前だけです。

完了したらこの部分をコピーして最初に作ったVite環境にペーストしてください。git add README.mdはgit add .に変更すると楽です。変更しなかった場合はvite-project全体をGithub Repositoryにpushしてください(.gitignoreされているものは除く)。

リロードしてファイルがこうなっていたら準備完了です。

Github Actionsを組んでいきます。node環境の準備、npm ciでパッケージのインストール、npm run buildでファイルのビルド、ビルドしたファイルをS3アップロードの順番で行うようにします。ファイルをS3に上げるには権限が必要なのでまずGithub Actionsで使うIAMユーザーを作成します。IAMのページからユーザーを追加で行えます。

ユーザー名を適当に入力して、CLIから使用したいのでアクセスキー・プログラムによるアクセスを選択します。

次に権限の追加です。本来もっと絞る必要がありますが、ここではS3に対するフルアクセスの権限を与えます。

他の項目もそのまま進めていくとIAMユーザーの作成が完了します。ここで表示されるアクセスキーIDとシークレットアクセスキーをGithubに登録します。

これらのキーをGithub Actionsで安全に使うためにActions Secretというところに保存します。

New Secretを押して、NameをAWS_ACCESS_KEY_IDとしてSecretにアクセスキーID、NameをAWS_SECRET_ACCESS_KEYとしてSecretにシークレットアクセスキーを登録します。Repository Secretに下のように表示されたら完了です。

権限関連の設定が終わったのでactionsのファイルを記述します。.github/workflowフォルダを作成して、deploy.ymlファイルを作成してください。先ほど記述した一連のタスクを記述したファイルがこちらです。
name: "Deploy"
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: npm
- run: npm ci
- run: npm run build
- env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: aws s3 cp --recursive --region ap-northeast-1 dist/ s3://vite-project
このファイルをmainに加えた瞬間から、mainに変更が加わるたびにビルドされたファイルがs3に反映されます。試しにApp.tsxの記述を変更してみます。地味ですがuseState(0)をuseState(1000)に変更してpushしてみましょう。
push後にdeploy(先ほど追加したactions)が動くことを確認できたら完了するまで待ちます。完了後に先ほど作成したページにアクセスしたところ、カウンターの初期値が1000になっていました。

加えた変更が自動で反映されるようになりましたね。これで完了です。
まとめ
最近ではVercel、AmplifyなどでGithubのリポジトリを読み取るだけでホストすることが可能ですが、あえてS3とCloudFrontを用いてホストしてみました。これによるメリットとしてLambda@edge、CloudFront Functionsが使えるなど詳細な部分の設定をできるところに利点があると考えています。難易度が高いわけではないので、詳細まで設定を組み込みたい方は是非この方法でご利用してみてはいかがでしょうか。