シングルページアプリケーション（SPA）をAWS S3+CloudFrontで公開する。


# 目的

シングルページアプリケーション(SPA)をAWS S3にデプロイし、所有しているドメインのWebサーバーとして公開する。
AWS S3にバケットを作ってアプリケーションをデプロイしCloudFrontを使って公開してみました。

公開予定のWebサーバーのFQDNを所有するドメインで、spa-study.colibrifw.org とします。
このFQDNはS３のバケット名を同じにし、DNSのCNAME設定でS3を別名にとして設定するとFQDN名でS3に直接アクセスすることもできます。
[CDN(コンテンツデリバリネットワーク)](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%83%87%E3%83%AA%E3%83%90%E3%83%AA%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF)である CloudFront を使うことでhttps化し、インターネットで公開するのに必須となる暗号化した通信を実現できます。

# 構築手順

### S3の構築

AWSコンソールにログインし、S3を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/7a7404fd-d1b7-e5e6-a027-a543409f2046.png)

「バケットを作成」をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/d423b443-3719-f3f9-c4d2-aff7dd224aba.png)

アクセス許可は後から変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/23f8da29-df71-6e73-31fa-deb21dff8398.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/6194f7f1-3bfd-aa43-838a-6b404437ea28.png)

「バケットを作成」ボタンをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/ff7d9300-ccd3-3f58-901d-0fa94e616653.png)

### SPAのビルドとアップロード

SPAはvue.jsです。他のSPAでも手順は同じだと思います。

vueアプリケーションをビルドします。

```shell
$ npm run build
...
entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  app (697 KiB)
      css/chunk-vendors.6ee33664.css
      js/chunk-vendors.b9cb5ef3.js
      js/app.b1571df1.js


  File                                    Size                                                   Gzipped

  dist/js/chunk-vendors.b9cb5ef3.js       349.56 KiB                                             113.26 KiB
...                                            6.08 KiB
  dist/css/chunk-3b23b504.01cf6fd8.css    2.09 KiB                                               0.69 KiB

  Images and other types of assets omitted.

 DONE  Build complete. The dist directory is ready to be deployed.
 INFO  Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html
```

ビルドすると `dist` ディレクトリができるのでその配下をS3にアップロードします。

syncを使うと開発側のディレクトリの状態と同期をとってくれます。

```
$ aws s3 sync dist s3://spa-deploy-study --delete --include "*" --acl public-read                                 
upload: dist/css/chunk-3b23b504.01cf6fd8.css to s3://spa-study.colibrifw.org/css/chunk-3b23b504.01cf6fd8.css
delete: s3://spa-study.colibrifw.org/js/app.0f92450f.js                             
upload: dist/index.html to s3://spa-study.colibrifw.org/index.html  
...
 $ 
```

では、画面から確認するためバケットのリストから作成したバケットをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/1bc3b1f5-3fab-6fe4-2b56-f96234b534db.png)

バケットの詳細より、アクセス許可をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/81e0fdd3-2460-b99b-2948-ed8a0e61702e.png)

ブロックパブリックアクセス(バケット設定)を次の値に変えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/28376e66-c81d-87a4-fe67-c2c7deaecb6f.png)

バケットポリシーを編集し、次のポリシーを設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/07fd0b89-bd28-4cde-c517-2f268288ddf0.png)

バケットのオブジェクトに戻り、任意のオブジェクトにチェックをつけて「URLをコピー」ボタンを押してブラウザに貼り付ければ次の様に表示されることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/c4ed9396-9ccd-d7f4-be6c-f04dc35f1537.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/f54001d8-b79a-373c-eb37-937d7cb5a17b.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/689b3083-e07c-df88-7549-c0bb7f3ecfcc.png)

また、DNSに上の様にバケット名がFQDNになる様なCNAMEを設定すると自分のドメインのホストの様に表示することができますがhttpでしかアクセスできないため保護されていない通信と表示されてしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/3d1ac0fc-6b3e-f3f1-74fe-f7a2812f5d99.png)


そこで、SSLの証明書を用意してCloudFrontで配信することでHttps化を実現します。

### SSL証明書の発行

SSL証明書は通常、CSRの鍵を作り有料で認証局に登録してもらう。認証局からもらった秘密鍵をWEBサーバーに組み込む。手間と時間とお金がかかります。

こちらにSSL証明書発行の流れが載っています。
[JPRS](https://jprs.jp/)の[常時SSL化について](https://jprs.jp/pubcert/about/aossl/)

AWSでは、AWSのサービスを使っていれば証明書を無料で発行してもらえます。
AWS Certificate Managerが証明書を発行するサービスです。

それでは手順を確認していきます。

メニューからAWS Certificate Managerを選択し、リクエストボタンをクリック。
画面では東京リージョンですが、CloudFrontで利用するためには(バージニア北部)リージョンの証明書が必要です。リージョンを切り替えてから作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/e6db347e-f7b9-138e-a3f6-f13f989cea13.png)

パブリック証明書をリクエストを選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/1e0c0767-8260-4b84-7758-b019d54a8a28.png)

完全修飾ドメイン名は、ホスト名FQDNを入れるか、先頭を*にして[ワイルドカード証明書](https://jprs.jp/pubcert/about/wildcard/)を発行することもできます。

DNS検証は、指定されたレコードをCNAMEとしてDNSに登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/52a8a69f-b058-f446-7d89-be50b1e63d34.png)

リクエストボタンをクリックすると証明書一覧が表示されます。この時点ではステータスが「保留中の検証」になっています。

一覧の証明書IDをクリックして証明書を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/d8336652-19b3-ed96-e5d4-de60980e2c72.png)

```
cname _4ec79a74cd75b1daccd25f9637e305c3.colibrifw.org. _fd22bfbe611e4d33e4c181e706686874.hcxvpdkkrx.acm-validations.aws.
cname spa-study-api _4ec79a74cd75b1daccd25f9637e305c3.colibrifw.org. 
```

ドメインに掲載されているCNAME名とCNAME値を次の様にCNAMEレコードとして設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/b58e78b3-32c1-6c20-fe3a-83ca2f7f0d1b.png)

少しすると証明書のステータスが「承認済み」に替わります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/1c841e9a-ea8c-40dc-6f98-8ea1f7941611.png)

### CloudFront作成

メニューよりCloudFrontを選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/f06c1e68-a48a-cb22-19ab-ed827161df3e.png)

「ディストリビューションを作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/dd70fa20-8983-fa99-fd5f-cbb7a6bf805f.png)

オリジンドメインは配布元になるドメインです。今回はs3でカーソルを当てると選択肢として現れるS3のバケットリストから選びます。
これを選択すると「名前」項目も自動で入ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/2319a87f-8f5d-89a8-3025-429d5481d000.png)

ビューワーというのはCDNにアクセスするクライアントでしょうか。HTTPSのみを選択しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/f19a51d0-45a8-1b05-3e6e-0171a926d626.png)


「料金クラス」は配信するエリアの広さで値段が変わると思われます。
代替ドメイン名(CNAME)は、CloudFrontで公開されるFQDNを記述する。
カスタムSSL証明書はAWS Certificate Managerで登録したものを指定します。
CloudFrontで使うためには（バージニア北部）リージョンで作成しなければならない様です。

ディストリビューションを作成ボタンをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/8129edeb-0638-9d94-3c79-5ed19f0ebf30.png)

ディストリビューションが作成できたら、DNSへ登録するためディストリビューションドメイン名をコピーします。
ディストリビューションドメイン名はCloudFrontのホスト名です。コピーしたホスト名をDNSのCNAMEに設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/d8354e31-22d4-13ab-38c2-cd9ba0e96c79.png)

設定後、ブラウザでアクセスし、SPAのページが見れ、SSL証明書のエラーもなければ成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/674caec8-61be-e55e-9af4-d8498475c18c.png)

### おまけ

SPAの中で画面描画だけでなく画面遷移をリダイレクトなどで行っているとき、AccessDeniedとエラーになることがあります。
これは、SPAの中で利用しているパスに対応したコンテンツがS3の中に存在しないことで発生するエラーの様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/36f25c77-d711-b617-3a20-adbec3e7385e.png)

この対策はディストリビューションの詳細画面からエラーメッセージタブを選び、カスタムエラーレスポンスを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/aac288bb-27a6-1ad0-6cec-691616c02916.png)

「カスタムエラーレスポンスを作成」ボタンをクリックするとエラーレスポンスを設定できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/60ecdcf2-2119-cde0-0514-98db156ef92b.png)

HTTPエラーコードは存在しないときに返却される403を選択
エラーレスポンスをカスタマイズありとし、レスポンスページのパスとしてSPAのデフォルトコンテンツを指定します。
レスポンスページを返す時のレスポンスコードは正常を表す200を選択します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284697/89993792-907a-3761-e421-f903f87d5cc6.png)