S3+CloudFrontを使ってReactアプリを独自ドメイン on HTTPSで公開する

# この記事について
この記事では、ReactアプリをHTTPSで公開するために、S3+CloudFrontでどのような設定・操作を行えばいいのかを解説します。
また、ドメインを独自のものにしたい場合には追加でどのような設定が必要かも示します。

## 使用する環境・バージョン
- OS: macOS Catalina 10.15.7
- aws-cli/2.0.10 Python/3.7.4 Darwin/19.6.0 botocore/2.0.0dev14

## 前提条件
- AWS CLIは使用できる状態にセットアップされているものとします。
- 独自ドメインを購入済み&そのドメインのホストゾーンがRoute53に存在済みとします。

## 構成概要
実はS3だけでもバケットの中身をWebに公開できる「静的ウェブサイトホスティング」という機能が存在します。
![スクリーンショット 2020-11-09 20.14.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/08689a59-d74f-e098-897c-25f1accc36c4.png)
しかし、S3のウェブサイトエンドポインとはHTTPSアクセスをサポートしていません。
そのため、S3の中身をHTTPSで公開したい！という場合は、CloudFrontというCDNサービスを噛ませる必要があります。

![スクリーンショット 2020-11-09 20.12.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/475dbdb3-4777-5954-a26c-20470c04c574.png)


# Reactアプリを作る
まずはReactアプリを作りましょう。今回はTypeScriptを使ってやってみます。

以下のコマンドを実行します。

```bash
$ npx create-react-app my-app --template typescript
```
すると`my-app`というディレクトリが作成され、その中にReact新規プロジェクトが作成されます。
あとはReactアプリを自由に開発しましょう。

# S3にアップロード
Reactアプリが出来上がったら、次にそれをS3にアップロードします。

## s3のバケットを作成
AWSコンソールでS3の画面を開きます。
![s3start.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/43bc9715-6920-2344-716f-ee29caa6d8dd.png)
右上の「バケットを作成」をクリックします。
すると、作成するバケットの設定画面に遷移します。

![スクリーンショット 2020-11-09 15.11.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/5357ad0c-131e-7922-c6f8-c9209974dbbf.png)
ここで名前を決めます(好きな名前をつけてOKです)。

![スクリーンショット 2020-11-09 15.11.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/b38d84fe-0a0c-84b8-f5ea-c969ab2508c8.png)
パブリックアクセスを全てブロックします。

その他はデフォルト設定のままにして、バケットを作成します。

## バケットにアプリをアップロード
S3にアプリをアップロードするために、作ったReactアプリを本番環境用にビルドします。
そのために、`my-app`ディレクトリ配下で以下のコマンドを実行します。

```bash
$ npm run build
```
すると、my-app配下に新たに`build`フォルダができ、その中に本番環境用に最適化・軽量化されたアプリのコードが格納されています。

この`build`フォルダ内のコンテンツを全て、以下のコマンドを用いて先ほどのS3にアップロードします。

```bash
# my-app配下で以下を実行
$ aws s3 cp ./build s3://[バケット名] --recursive
```

## (おまけ)静的ウェブサイトホスティング設定はしなくていいの？
バケットを選択→プロパティタグ→静的ウェブサイトホスティングという設定があります。
![スクリーンショット 2020-11-09 15.43.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/d94537df-4471-2ae8-cd72-5fab3abb1626.png)
![スクリーンショット 2020-11-09 15.43.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/ebeeeb6d-e877-e919-bac6-a195ab8438a9.png)
構成概要のところで述べた通り、これを使えばS3の静的ホスティング機能でのコンテンツ公開ができるようになります。
CloudFrontを噛ませる場合は、これが無効でも大丈夫です。

# CloudFrontでS3の中身を配信
次に、このS3の中身をCloudFront経由で配信するために設定を行います。

CloudFrontを開くと、以下のような画面になります。
![スクリーンショット 2020-11-09 15.51.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/0ff86077-88ad-c7e8-9bae-61e1d038d53c.png)
画面中央の"Create Distribution"ボタンをクリックします。

![スクリーンショット 2020-11-09 15.54.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/898f99fc-9a6c-fcdf-6b65-c8e54452f3b6.png)
今回はWebサイトを配信したいので、WEBディストリビューションの方で"Get Started"をします。

## Origin Settings
まずは、配信するコンテンツそのものに関する設定を行います。
![スクリーンショット 2020-11-09 15.59.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/7fc6516c-67cf-9f7f-2a54-6ac58a6e2ff4.png)
まずは、"Origin Domain Name"のところに、さっき作成したS3バケットを指定します(入力フォームにフォーカスすると、プルダウンメニューが出てくるので、その中から先ほどのS3バケットを選択しましょう)。

すると、OriginIDが自動的に埋まり、以下のように新しい設定入力タブが出現します。
![スクリーンショット 2020-11-09 16.06.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/9cb6a060-b8c9-650c-b945-59d55edd23f3.png)
そして、以下のように設定を入力します。

- Restrict Bucket Access: Yes
- Origin Access Identity: Create a New Identity
- Grant Read Permissions on Bucket: Yes

Restrict Bucket Accessは、S3コンテンツへのアクセスを、CloudFront経由のみに制限するかどうかの設定項目です。これをYesにすると、S3バケットに直接アクセスして中身をみようとする行為をブロックすることができます。

Origin Access Identity(OAI)は、S3へのアクセスがCloudFront経由かどうかを識別させるためのIDです。Distribution作成に伴って、それに関連づけたいOAIも新規作成したいので、"Create ~"を選択します。
設定フォームのCommentは、OAIの名称(説明)を書くところです。これはデフォルトのままでもいいですし、「何月何日に〜のために作ったOAI」と自分で書いてもいいです。

Grant Read Permissions on Bucketは、ここで指定したOAIにS3の閲覧権限を与えるかどうかの項目です。Yesを選択すれば、自動的にS3のアクセス権限をアップデートしてくれます。

## Default Cache Behavior Settings
この部分の設定はデフォルトのままで大丈夫でしょう。

特筆すべき点としては、"Viewer Protocol Policy"のところで、HTTPアクセスを許可するかブロックするか、強制的にHTTPSに直すかを選択することができます。ここは好きに選択すればいいでしょう。
![スクリーンショット 2020-11-09 16.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/2302d197-c1d2-1f42-541f-4461b4c23036.png)

## Distribution Settings
![スクリーンショット 2020-11-09 16.11.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/111ada6d-2137-5fc0-75ad-3380a3b9ee6b.png)
ここでは、以下の部分の設定を追加します。

- Default Root Object: index.html

これで、アクセスが来た場合は、S3中の`index.html`にデフォルトで行くようになります。

## デプロイ
ここまで設定できたら、画面下部の"Create Distribution"ボタンをクリックします。

すると、以下のようにエッジサーバーへのコンテンツ配信が始まります(StatusがIn Progressのときは配信中です)。
![スクリーンショット 2020-11-09 16.15.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/259e41a2-b65e-8b8f-414d-7f5c725d8017.png)
しばらく時間をおくとStatusがDeployedに変わります。これで準備は終わりです。
![スクリーンショット 2020-11-09 16.18.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/6361a80c-68e0-d167-b279-4115497f8604.png)
この状態になったら、Domain Nameに表示されているURL`https://xxxxxxxxxx.cloudfront.net`にアクセスしてみましょう。

すると、S3にアップロードしたReactアプリがHTTPSで見れるようになっているのがわかると思います。

# 独自ドメインにしたい
今のままでは、アプリが公開されているURLがCloudFrontデフォルトの`https://xxxxxxxxxx.cloudfront.net`のままです。
ここからは、これを既に持っている独自ドメインに変更する過程を紹介します。

## AWS Certificate Managerで証明書発行
独自ドメインでもHTTPSで通信できるように、証明書を発行します。
このとき、リージョンを**us-east1**にしてから作業を行ってください。
(CloudFrontに利用する証明書はus-east1発行のもののみ使えるので、ap-northeast-1のままだとNGです)

AWS Certificate Managerを開くと以下の画面になります。
![スクリーンショット 2020-11-09 22.08.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/c655c7fb-a2e0-4568-5a98-b4e02d6ae8bd.png)
パブリック証明書のリクエストを選択します。

![スクリーンショット 2020-11-09 22.10.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/66840475-0713-c8a8-b0cc-437b8d495095.png)
ここで、証明書で認証したいドメインを指定します。
例えば、`example.com`という独自ドメインを持っていた場合、ドメイン名のところには「`*.example.com`」を入力しました。

![スクリーンショット 2020-11-09 22.22.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/f22d7b22-983b-5a1b-62a3-53332a99a937.png)
先ほど入力したドメインが有効なものかどうかをどうやって検証するかをここで選びます。
今回は「DNSの検証」を選択します。

タグの追加はせずに次に進むと、以下のような画面になります。
![スクリーンショット 2020-11-09 22.25.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/229ae3ea-6d3b-5a03-e3c0-2052af41e944.png)
「ドメインのDNS設定に次のCNAMEレコードを追加します」という表示があります。先ほどのドメインの「DNS認証」というのは、今画面に表示されているCNAMEレコードをDNSサーバーに書き込むことで認証が行われる方法なのです。

「Route53でのレコード作成」ボタンをクリックします。
![スクリーンショット 2020-11-09 22.26.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/26cd4308-edcc-5e9c-76b3-18175e47bd6e.png)
それほど時間が掛からずに成功表示が出ます。これで、Route53に自動でCNAMEレコードが入りました。

「続行」ボタンを押すと、証明書の申請は完了です。
![スクリーンショット 2020-11-09 22.27.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/b0eeca83-edff-5e3b-35d1-0c099697bb85.png)
最初は検証保留中の状態ですが、しばらく待つと発行済みになります。これで証明書が準備できました。
![スクリーンショット 2020-11-09 22.35.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/681677d6-7984-7cdc-6aa8-83e957aa5f72.png)

## CloudFrontで独自ドメインを使用するよう設定
先ほどCloudFrontで作ったDistributionを開き、Settings→Editを選択します。
![スクリーンショット 2020-11-09 22.47.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/48ea3577-5036-3210-0646-1cd7dd7f6091.png)
すると、以下のような設定編集画面が現れます。
![スクリーンショット 2020-11-09 22.49.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/d459f020-e5e8-9b07-d335-97dcae059464.png)
ここで、以下のように設定します。

- Alternate Domain Names: `www.[独自ドメイン]`
- SSL Certificate: Custom SSL Certificateを選択して、先ほど作った証明書を指定します。
- Custom SSL Client Support: SNI

この状態で、設定を保存します。

## Route53の設定
あとはRoute53の方で、「独自ドメインは、CroudFrontのデフォルトURLのエイリアスである」という情報の登録を行えばOKです。

Route53の独自ドメインホストゾーンにてレコードを作成します。
![スクリーンショット 2020-11-09 23.12.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/53bbc11e-c610-afae-6647-21fd914a2c7d.png)
すると、以下のような画面になります。
![スクリーンショット 2020-11-09 23.12.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/7766a445-c202-2151-442b-77c300b092cd.png)
「シンプルルーティング」を選んで「次へ」をクリックします。
![スクリーンショット 2020-11-09 23.13.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/41ecd677-b430-1404-6483-c5c8efd70d61.png)
「シンプルなレコードを定義」をクリックして、レコード追加の設定を行います。
![スクリーンショット 2020-11-09 23.13.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/06c7c23a-f50f-d648-fe09-234134d2325e.png)

- レコード名: `www.[独自ドメイン]`
- 値/トラフィックのルーティング先: CloudFrontへのエイリアス/米国北部/`xxxxxxxxxx.cloudfront.net`(デフォルトのCloudFront URL)
- レコードタイプ: A

このように入力したら、「レコードを定義」をクリックして保存します。

## テスト
これで独自ドメインでの公開準備は全て終了です。
試しに、独自ドメイン(`https://www.example.com`)でブラウザからアクセスしてみると、S3の中身が表示されるはずです。

# 片付け
CloudFrontは使いっぱなしだとお金がかかるので、いらないのなら削除してしまいましょう。

CloudFrontの使用をやめたい場合は、コンソール上でDistributionをDisable→DeleteとすればOKです。
![スクリーンショット 2020-11-09 16.20.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520877/2346091b-110d-0f44-1f68-1e20ac934fd5.png)

# 参考文献

- [GitHub ActionsでReactアプリをS3にデプロイ（+ Cloud Frontのキャッシュクリア）して、Slackに通知するまで](https://changeofpace.site/プログラミング/2020/03/01/github-actions-react.html)
- [CloudFront + S3でReactのSPAを作成する方法](https://confrage.jp/cloudfront-s3でreactのspaを作成する方法/)
- [ACMでSSL証明書を発行](https://www.wakuwakubank.com/posts/684-aws-acm-ssl/)
- [[ACM] AWS Certificate Manager 無料のサーバ証明書でCloudFrontをHTTPS化してみた](https://dev.classmethod.jp/articles/acm-cloudfront-ssl/)
- [CloudFrontでS3のウェブサイトをSSL化する](https://qiita.com/jasbulilit/items/73d70a01a5d3b520450f)
- [静的サイトをS3に公開 ＆ 独自ドメイン・HTTPS化させる](https://qiita.com/kskinaba/items/9c48cc216991a548ddb6)