Cloudfrontを用いて、ネストしたパスへのアクセスをS3にホストしたReactに飛ばす方法

SPAのS3ホスティングはアプリの手軽な配信方法として大変有用です。
筆者も御多分に洩れず、S3ホスティングとCloudFrontを併用して、とあるURLにアプリを展開することがよくあります。
ある時、とあるドメインのとあるパスの直下にアプリをデプロイしたい場合があったのですが、単純にドメイン直下にアプリをデプロイするのとは異なり、いくつか設定が必要なことがわかりました。
この記事ではその方法をシェアしたいと思います。

今回は、`https://test.asahi.com/demo/`というURLでアプリケーションにアクセスできるようになることを目指します。

# S3バケットの準備
### S3バケットの作成
S3バケットを作る際は、「このバケットのブロックパブリックアクセス設定」という項目のチェックを全て外してください。
これにより誰もがデプロイしたサイトにアクセスできるようになります。
逆に限られた環境でのみ公開したい場合は、絶対にこれをやってはいけません。
その場合の詳しい設定方法は既に記事がたくさんあるので、検索してみてください。

作成が完了したらバケットのアクセス許可のタブから、「バケットポリシー」を更新します。
デフォルトでは何も記述されていないはずなので、以下の内容で更新してください。
一部バケット名を記載する部分がありますので、ご注意ください。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::ここにバケット名/*"
        }
    ]
}
```

### S3バケット上にDir構造を作る
次にS3にデプロイするURLと同じ構造のDir構造を作ります。
今回は`/demo`にデプロイするので、適当なS3バケットを作った後に`demo`というフォルダーを作ればOKです。
仮に`/demo/hoge`にデプロイしたいのであれば、`demo`の中にさらに`hoge`というフォルダーを作ればOKです。

### 静的ウェブサイトホスティングの有効化
次にS3バケットの設定からホスティングを有効化します。
プロパティのタブから「静的ウェブサイトホスティング」の編集画面に進みます。
その先で静的ウェブサイトホスティングのラジオボタンを「有効にする」にした上で、インデックスドキュメントに`index.html`を指定します。
![スクリーンショット 2022-12-15 14.21.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677068/90bf0df7-00c0-3b5c-3cf8-c32eb000f388.png)

これでS3の設定は完了です。

# Reactのbuild物をS3に置く
### Reactの設定の注意点
今回は`https://test.asahi.com/demo`というパスにアプリケーションをデプロイすることを考えています。
この場合、次の２つの設定が必要になります。

1. package.jsonにhomepageを追加してそこに配信するドメインのパスを指定
    package.jsonに`homepage`の項目を追加し、そこにベースのURLを記述します。
    ```json
      "homepage": "https://test.asahi.com/demo"
    ```

2. react-router-domを使っている場合の設定
    Reactでルーティングを実装する場合、react-router-domを利用することが多いと思いますが、その場合も設定することがあります。
    Appの直下で利用する`BrowserRouter`で`basename`を以下のように指定する必要があります。
    ```jsx
    <BrowserRouter basename="/demo">
    ```
    これによって、サイトのベースパスに`/demo`を設定することができます。


上記２点を設定した上で、最初に用意したS3バケットの`/demo`フォルダーにReactのbuild物を全て置きます。
Webコンソール上から手動でファイルをアップロードすることができます。

以上までの設定で、下のURLのようなバケットウェブサイトエンドポイントからアプリにアクセスできるようになっているはずです。
`http://バケット名.s3-website-ap-northeast-1.amazonaws.com/demo/`

このURLは、静的ウェブサイトホスティングの項目から確認できます。


# CloudFrontの設定
最後にCloudFrontの設定を行っています。
ディストリビューションの作成方法に関しては、ここでは詳しく解説しませんので、詳しくは検索してみだくさい。
この記事では、`https://test.asahi.com`というドメインを利用することにしていますので、代替ドメイン名とカスタムSSL証明書の設定あたりが重要になってくるはずです。

参考になりそうな記事を共有しておきます。

https://zenn.dev/kwappa/scraps/a456e6841dfb0d

以降では上の設定が上手くいったと仮定して、その後のオリジンとビヘイビアの設定方法を説明します。

### オリジンの作成
ここではオリジンドメインと名前を設定するだけでOKです。
オリジンドメインには、上で確認したバケットウェブサイトエンドポイントを設定します。
具体的には`バケット名.s3-website-ap-northeast-1.amazonaws.com`のようなイメージです。

名前はご自身が識別しやすいものにしてください。
ここでは仮に`demo app`という名前を付けたとします。

### ビヘイビアの作成
ここではパスパターンとオリジンとオリジングループの２つの設定がキモです。
パスパターンに関しては今回の想定では以下のようにします。
`/demo/*`
これによって`/demo/`で始まるパスが次に設定するオリジンに流れるようになります。


オリジンとオリジングループには上で作成したオリジンを設定します。
今回で言うと、`demo app`がそれに相当します。

それ以外に関しては、基本そのままの設定で問題ありませんが、自分の場合は、
オブジェクトの圧縮をNoに、
ビューワープロトコルポリシーをRedirect HTTP to HTTPSに設定することが多いです。

以上の設定で`https://test.asahi.com/demo/`からアプリにアクセスできているはずです！
お疲れ様でした。

# この方法でデプロイしたのがこちら！
上の設定で公開したのがこちらのサイト、「**朝日新聞Playground**」になります！

https://cl.asahi.com/playground/

私の所属する[メディア研究開発センター](https://cl.asahi.com)で開発された技術や遊べるデモなどを順次公開していきますので、ブックマークしてときどき遊びに来ていただけると嬉しいです。
この記事の公開時点では、AI要約とAI校正の２つのデモを試すことができます。


# 最後におまけ
上の手順で一応の設定は完了したのですが、まだ詰めが甘い部分があります。
それらの設定方法について簡単に説明します。

### アプリ内のとあるルートパスに直接アクセスしたい場合の設定方法
Reactアプリ内でルーティングを利用している場合は、直接そのルーティングにアクセスしたい場合があると思います。（例えば今回の例だと`https://test.asahi.com/demo/hoge`など）
S3ホスティングを利用したSPAサイトではそのURLにアクセスしても、403や404などが返ってきてページがきちんと表示されません。
そのような場合はこちらのページを参考にしてみてください。

https://zenn.dev/redgosho/articles/13b93d2a8eac0f

ただ上の記事の場合、本当は403や404が帰ってきてほしい場合でも200が帰ってきてしまうので、その点には注意です。

### URLの最後に`/`がなかった時に`/`があるページにRedirectさせる
今回の設定では、`https://test.asahi.com/demo`にアクセスがあっても、パスパターンの対象外であるためにアプリに上手く飛ばないようになっています。
URLの最後に`/`がなかった時にもアプリにアクセスできるようにするには、最後に`/`がないパスパターンのビヘイビアを作ればOKです。
上で説明したビヘイビアの作成において、パスパターンを入力するところで最後の`/`がないパスパターンを入力すれば良いだけです。

### s3のホスティングされているURLに直接アクセスできないようにRefererを設定する
現状のままだと、
`https://test.asahi.com/demo/`と
`http://バケット名.s3-website-ap-northeast-1.amazonaws.com/demo/`の
どちらからでもアプリにアクセスできてしまいます。

例えばそのアプリからなんらかのAPIを利用する場合に、そのAPIのAccess-Control-Allow-Originに`test.asahi.com`が設定されていると、バケット自体のURLからのアクセスではAPIを利用することができません。
このようなことを避けるために、バケットのURLからはアプリにアクセスできないようにすることができます。

詳しくは下記リンクを参考にしてみてください。

https://laboliy.com/cloudfront-s3-static-restricted-by-referer/

### Github Actionsを利用したデプロイの自動化
今回はbuild物を手動でS3にアップロードしましたが、デプロイの度にこの作業をするのは大変です。
そこでソースコードをGithubで管理している場合は、Github Actionsを利用してデプロイ作業を自動化しましょう。
こちらに関しては下記リンクが詳しいので、参考にしてみてください。

https://dev.classmethod.jp/articles/deploy-web-site-with-github-actions/