S3 CloudFront Route 53 でReactで作ったSPAを配信する

# この記事のゴール

S3 CloudFront Route 53 を使ってReact製のSPAを独自ドメインで配信します。

# 前提条件

以下の条件を満たしている前提で話を進めていきます。

1. Route 53 で独自ドメインを購入している事
1. AWS Certificate Managerで独自ドメインの証明書を取得している事（証明書は `us-east-1` に作成されている必要があります。）
1. 利用するSPAはサーバーサイドレンダリングが考慮されていない仕様である事

`1` に関しては別に Route 53 で購入したドメインでなくても大丈夫なのですが、全ての手続きがAWS上で完結すると楽なので、この記事ではそのようにしています。

`2` に関しては現時点ではCloudFrontに設定出来る証明書が `us-east-1` に配置されていないと設定出来ない為です。（そのうち他のリージョンの証明書も使えるようになると信じています。）

`3` に関してはReact + ReactRouter + Redux で作成していますが、vuex と vue-router等でも同じ要領で設定可能かと思います。

以前、[AWSのサービスでドメインを取得しALBでSSLで接続出来るようにする](https://qiita.com/keita-nishimoto/items/4b2db95eae81044a779c) という記事を書きました。

ドメインの購入や証明書の設定等はこちらも参考にして頂ければ幸いです。

# SPAをデプロイする為のS3バケットを作成する

SPAをデプロイする為のS3バケットを作成します。

マネジメントコンソールから「Amazon S3」を選択し、バケットを作成します。

バケット名は何でも良いですが、デプロイ対象の格納用と分かる名前を付けましょう。

この設定で注意するのは一点、「Static website hosting」を無効にするという点です。

![Staticwebsitehosting.png](https://qiita-image-store.s3.amazonaws.com/0/71899/3f89dabd-e739-5248-5604-e16d57316348.png)

これが有効になっていると、CloudFrontを経由せずに直接S3バケットのhtml にアクセスされてしまうので無効にしておきます。

# S3バケットにSPAのソースコードをデプロイする

作成したS3バケットにSPAをデプロイします。

私の場合は webpackを利用してJavaScriptファイルを 「bundle.js」 という名前で1つにファイルにまとめているので、index.html と bundle.js の2つのファイルをアップロードします。

※ `bundle.js` は distの中にあります。

![s3dirls.png](https://qiita-image-store.s3.amazonaws.com/0/71899/8580f39a-ac62-fa74-cf68-8f36391ab8b1.png)

# CloudFront Distributionsを作成する

マネジメントコンソールでCloudFrontのページに遷移します。

`Create Distribution` より新規作成を行います。

![CloudFront_1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/1ae57b6c-60a7-b47a-7662-7f112c08c60e.png)

今回作成するのはWebなのでWebの「Get Started」を選択します。

![CloudFront_2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/fb857c3d-7f08-9daf-8186-898149d05ad9.png)

## Origin Settings

「Origin Domain Name」に作成したS3バケットを選択します。

Restrict Bucket Accessを「Yes」に設定します。

「Origin Access Identity」は通常何もないハズなので新規作成を行います。

「Grant Read Permissions on Bucket」 は 「Yes, Update Bucket Policy」を選んでおきます。
これを選択する事で、S3バケットのバケットポリシーにCloudFront経由でのアクセスを許可する設定を自動で書き込んでくれます。

![OriginSettings.png](https://qiita-image-store.s3.amazonaws.com/0/71899/7d19de41-06a1-585e-b4af-0210f6e971fa.png)

※ 全ての設定が終わった後でS3バケットのバケットポリシーを確認して見ましょう。以下のようなバケットポリシーが書き込まれているハズです。

```json
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity AAAAAAAAAAAAAA"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket-name/*"
        }
    ]
}
```

## Default Cache Behavior Settings

ここは配信するWebアプリケーションの要件によって変化すると思いますが、私は下記のように設定しました。

![DefaultCache BehaviorSettings.png](https://qiita-image-store.s3.amazonaws.com/0/71899/8e44c1fd-8eca-c1ef-ce93-c8dba5262006.png)

「Viewer Protocol Policy」 は HTTPS Only を選択しました。
HTTPS以外ではアクセスさせないようにする為です。

「Allowed HTTP Methods」は GET, HEADのみ許可しました。私のアプリケーションでは、それ以外のHTTPメソッドを許可する必要性がない為です。

このあたりは配信するWebアプリケーションの要件に合わせて設定して頂ければと思います。

## Distribution Settings

下記のように設定します。

![Distribution Settings.png](https://qiita-image-store.s3.amazonaws.com/0/71899/73bffa03-7e24-6943-58b8-de4272e0c47e.png)

Default Root Object に 「index.html」を追加しました。これはデフォルトで参照対象にするファイルの指定です。

「Supported HTTP Versions」 は特に理由がなければHTTP2のサポートを含んだ上の項目を選択しておけば良いでしょう。

「Enable IPv6」 に関してはIPv6に対応させる必要がなければ外しても良いと思います。

要件に合わせて設定を行って下さい。

その他の項目はデフォルトでOKです。

# DNSレコードを登録する

Route 53 → 「Hosted zones」 → 「Create Record Set」 よりレコードセットの作成を行います。

このあたりの手順は以前、私が書いた[こちらの記事](https://qiita.com/keita-nishimoto/items/b56a8e9abeaf6724a28b#%E3%83%87%E3%82%A3%E3%82%B9%E3%83%88%E3%83%AA%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E5%90%8D%E3%82%92route-53%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B) と同様で作成したCloudFrontのドメイン名をAlias ターゲットに指定します。

![Route53.png](https://qiita-image-store.s3.amazonaws.com/0/71899/635c1c4b-90e2-b8de-4bd3-51bb2573f237.png)

CloudFrontのドメイン名に関しては マネジメントコンソール → 「CloudFront」 → 「作成したDistribution」 → 「General」 → 「Domain Name」 より確認出来ます。

作成するレコードタイプをCNAMEにしても実現は可能ですが、Aレコードのほうがパフォーマンスや料金面でお得だそうです。

下記のクラスメソッドさんのブログに説明がありましたのでこちらに掲載させて頂きます。

https://dev.classmethod.jp/cloud/aws/amazon-route-53-alias-records/

# CloudFront Distribution Settingsに独自ドメインを追加

先程作成した独自ドメインを CloudFront Distribution Settingsに追加します。

「Distributions」 → 「作成したDistribution」 → 「General」 → 「Alternate Domain Names (CNAMEs)」 に先程作成した独自ドメインを追加します。

![domain-add.png](https://qiita-image-store.s3.amazonaws.com/0/71899/10c031a7-89bb-6eb5-462a-e602116d42cf.png)

設定は以上で完了です。

アクセス出来るかブラウザで確認します。

CloudFrontのドメイン名が `aaaaaaaaaaaaa.cloudfront.net` , 独自設定したドメイン名が `example.com` の場合は2つで同じ内容が表示されるか確認を行います。

- https://aaaaaaaaaaaaa.cloudfront.net
- https://example.com

同じコンテンツが表示されたら設定は成功しています。（反映までに少し時間がかかります。）

# Error Pagesの作成

これで最低限の設定は完了したのですが、React Router等を使っている場合、例えば https://example.com/login のようなパス付きのURLにアクセスするとXML形式の403ページが表示されてしまうかと思います。

これは対象のディレクトリがS3バケットに存在しない為に発生します。

このままではマズイので CloudFrontのError Pagesに設定を追加する事で回避します。

「Distributions」 → 「作成したDistribution」 → 「Error Pages」 より 「Create Custom Error Response」 を作成します。

下記のように403, 404の2種類を作成します。

![403.png](https://qiita-image-store.s3.amazonaws.com/0/71899/d3464c5e-0168-38fc-589b-94cfe9179cca.png)

![404.png](https://qiita-image-store.s3.amazonaws.com/0/71899/8e54d342-e1e7-9588-47c0-38ee2d88f501.png)

こうする事で 403, 404 エラーが発生した際に `index.html` を読み込むようになります。

これでSPAの配信が出来る最低限の設定は完了となります。

# まとめ

以上が S3 + CloudFront + Route 53でSPAアプリケーションをホスティングする為の最低限の手順となります。

最後まで読んで頂きありがとうございました。