Flutter for Web + AWS S3 + CloudFront + 独自ドメインで始める爆速PWA

# はじめに
Flutter for Webを使っておよそ3時間でPWAサービスをリリースします！

ちょっと前までFlutterの名前も知らず、知ってからもiOS/Androidクロスプラットフォームという印象でした
しかし、[2021年3月のFlutter2リリース](https://developers.googleblog.com/2021/03/announcing-flutter-2.html)で正式にWeb対応したということを知りまして、将来性しか感じておりません！

ということで独自ドメインとAWSを併用して爆速でPWAサービスをリリースしてみました

## PWAとは
Progressive Web Appsの略で、ウェブサイトにも関わらずネイティブアプリのような挙動を実現する機能です
アプリストアにリリースすることもなく、スマホのホーム画面にインストールできたり、プッシュ通知ができたりします

ブラウザのUIがなくなる分、画面を最大限使えるのが個人的にはイチオシのポイントです

↓は今回作成したサンプルページの例ですが、PWA化した方が上下の表示領域が広いです
![sample_web.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/e2895cbe-5a7e-0144-3d7c-4994e92b450d.png). ![sample_pwa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/e7a8529d-23d7-4b45-5e2e-ad5f4aa1386f.png)

できることは限られますが、ストアの審査も要らずにネイティブ風のアプリが作れるのはアツいのではないでしょうか

ちなみにサンプルは[こちらに置いておきます](https://flutter-pwa-sample.site)

# 開発環境
* Flutter 2.0.6
* MacOS Big Sur 11.2.3
* AndroidStudio 4.1.2

ちなみにApple M1チップですが、特に問題ありませんでした

# 作る
## Flutterのサンプルアプリを作る
### Flutterの導入
Flutterそのもののインストールについては公式もありますし、他の方も書いていると思うので割愛します

[Flutter公式](https://flutter.dev/docs/get-started/install)を参照しながら、

```
$ flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.0.6, on macOS 11.2.3 20D91 darwin-arm, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.1)
[✓] Connected device (1 available)

• No issues found!
```

コマンドが全て問題なく通るようにしてあればOKです

加えて、AndroidStudioをエディタとして使うのが個人的に好きです
こちらもやり方は割愛しますが、AndroidStudioにFlutterとDartのプラグインを入れることになります

※参考[【Flutter】 アプリ開発入門 Hello Flutter!!](https://qiita.com/tetsukick/items/cdecf64172c5f4a126d6)

### Flutterプロジェクトを作成する
AndroidStudioを起動し、「Flutter Application」を選択してプロジェクトを作成します

![スクリーンショット 2021-05-02 15.46.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/db7ed0db-a983-1765-d45e-e36e31b98fc4.png)

Web向けのリリースだけならば、iOSやAndroidのコードについてはチェックを外してもOKです
![スクリーンショット 2021-05-02 15.47.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/18e3d68f-0da5-3666-3a11-4a65798aa009.png)

プロジェクトを作成したら、ターミナルでFlutter for Webを有効化します

```
$ flutter config --enable-web
```

Flutter for Webが有効化されていると、デバッグデバイスにChromeが追加されます

```
$ flutter devices

1 connected device:

Chrome (web) • chrome • web-javascript • Google Chrome 90.0.4430.93
```

### 動かしてみる
AndroidStudioに戻ってFlutterプロジェクトを開くと、デバッグ対象にChromeが追加されています
この状態でデバッグボタンを押せば、勝手にChromeが起動してアプリが動きます

![スクリーンショット 2021-05-02 15.49.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/8c194ab6-8465-485a-5f27-8f582d95141b.png)

doctorをオールOKにするのがやや手こずりますが、環境構築はものすごく簡単ですね...
![スクリーンショット 2021-05-02 15.50.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/b9674ed0-224d-2cad-bdd4-b45ee1477a4d.png)

### ビルドする
動くことが確認できたら、Web向けにビルドします。下記のコマンドを叩くだけ

```
$ flutter build web
```

するとbuild/web/以下にindex.htmlに一通り出力されます
しかもこの時、manifest.jsonが生成されていまして、なんと**デフォルトでPWA対応しています。**
あらやだFlutter恐ろしい子
![スクリーンショット 2021-05-02 16.01.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/d39a87bb-6a8b-734d-a02a-b13399118386.png)


もしindex.htmlがないよ！みたいなエラーが出たら、下記のコマンドを叩いて再度やってみてください

```
$ flutter create .
```

アプリルート以下のwebディレクトリが丸ごとできていないケースがあるようです
![スクリーンショット 2021-05-02 16.00.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/a6fcff30-4bd4-b125-464e-7ffb2cd46d04.png)

Flutterの出番は一旦ここまでです

## AWS S3 + CloudFrontでFlutter for Webを静的ホスティングする
ちなみにS3への直アクセス制限（Restrict Bucket Access）もやります

### S3バケットを作成する
適当な名前でS3バケットを作成します
![スクリーンショット 2021-05-02 16.03.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/415c9226-c076-70f6-9eec-70339eadbb13.png)

パブリックアクセスはブロックでOKです。後ほどCloudFront経由のみを許可します
![スクリーンショット 2021-05-02 16.10.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/f2687973-8bce-1b52-65ad-bd6da45ac3d1.png)


### Flutter for Webをアップロードする
作成したS3バケットに、flutter build webで生成されたbuild/web/以下のファイルを全てアップロードします

![スクリーンショット 2021-05-02 16.05.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/09fef354-91c5-b228-6063-c826b1a11f15.png)

### CloudFrontからS3にアクセスする
CloudFrontのディストリビューション作成から、新規作成します

* OriginDomainName : 先ほど作成したS3バケット
* Restrict Bucket Access : Yes  ※OriginDomainNameでS3を指定すると出てくる
* Origin Access Identity : Create a New Identity
* Grant Read Permissions on Bucket : Yes

![スクリーンショット 2021-05-02 16.07.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/9d37f3c9-5148-d040-41c2-f4b2c551c3bd.png)

作成に成功すると、S3の「アクセス許可」の「バケットポリシー」に下記が追加されます

```
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity xxxxxxxxxx"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::バケット名/*"
        }
    ]
}
```

ちなみに[S3静的ホスティング時のポリシーが残っているとアクセス拒否されることがある](https://dev.classmethod.jp/articles/tsnote-s3-block-public-access/)ようなので、S3にアップロードした時点でホスティングして確認した人はご注意ください

そのほか画像には無いですが、下記の設定をしています

* 「General」→「Edit」→Default Root Object : index.html
* 「Behaviors」→「Edit」→Viewer Protocol Policy : Redirect HTTP to HTTPS

この時点で**CloudFrontのDomainNameからアクセスできることを確認してください**

## ドメインを取得する
お好みの独自ドメインを取得します
今回はムームードメインで適当に「flutter-pwa-sample.site」を取得しました
１８４円/年と激安

## HTTPS対応する
PWA化するにはHTTPS化が必要です

### Route53でホストゾーンを作る
取得したドメインを使ってRoute53でホストゾーンを作ります
![スクリーンショット 2021-05-02 16.51.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/e69dea4d-22a8-62f7-a6f2-488be005bac4.png)

### 証明書を発行する
ホストゾーンができたら証明書を作成します
CloudFrontから証明書を参照するために、**ここだけ北部バージニアで行ってください**
AWS Certificate Managerで取得したドメインを入力し、
![スクリーンショット 2021-05-02 16.57.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/7541f5a0-8dea-dd7e-ec1a-96635ab0f0fe.png)

「DNSの検証」
![スクリーンショット 2021-05-02 16.57.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/a48351ba-63ee-1d52-e683-c8ba611f8222.png)

「Route53でのレコードの作成」をすると、先ほどのホストゾーンにCNAMEレコードが追加され、自動的に検証が始まります
![スクリーンショット 2021-05-02 16.58.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/52ba4b70-9cac-6e8b-5b87-7dac3ff9702e.png)

「発行済み」になればOKです。私の場合は数分程度で完了しました

### CloudFrontに証明書を紐づける
CloudFrontのディストリビューション設定を開き、下記を設定します

* Alternate Domain Names : ドメイン名
* Custom SSL Certificate : 先ほど作成したSSL証明書

![スクリーンショット 2021-05-02 17.07.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/e37f454c-30dc-8b06-9aab-9f1733ee9700.png)

### ルートレコードを作る
Route53でドメイン名ルートのレコードを作ります

![スクリーンショット 2021-05-02 17.11.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/3bd87217-7679-af34-e873-e5c530683179.png)

## 独自ドメイン側を設定
最後に、独自ドメイン側にRoute53のネームサーバを設定して終了です

Route53には最終的に4レコードあることになりますが、NSレコードの値を独自ドメイン側にも設定します
![スクリーンショット 2021-05-02 20.05.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/7134cbc2-084a-4d88-b344-12f5f6ab09b5.png)

ムームードメインの場合は、「コントロールパネル」→「ドメイン名」→「ネームサーバの設定変更」から、↓のような感じで設定すればOKです
**末尾のピリオドは不要**なのでご注意ください
![スクリーンショット 2021-05-02 16.53.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/56c738cb-9603-2f1b-9501-f2ca3da0d304.png)

数日かかるかも！と書かれていますが、私の場合はごはん食べている間に反映されていました

# 完了！
ということで、証明書発行やネームサーバ設定の反映待ちの時間を入れても2~3時間で、PWAサービスを公開することに成功しました！
Safariの場合はサイトを開いて「ホーム画面に追加」するだけで、ネイティブアプリのような見た目で表示することができるようになります

![sample_web.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/4bc5cad5-f28e-bdf9-60b8-77870eea43cb.png). ![sample_pwa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/1b1df343-28e4-4a55-4d97-4c05ff58f0c1.png)

FlutterはUIの構築もやりやすく、個人や少人数チームであれば超有力な選択肢になると思っています
（今回はWebですが、Win/MacOS/Linuxへもリリースできる）

Web歴１年ちょいのまだまだ素人ですが、精進していきたいと思います