Azure Static Web Appsの始め方

Azure Static Web Appsを使い始めるときの記録記事です。

# はじめに

Jamstackいいですよね。

始祖である [Netlify](https://www.netlify.com/) 使っても良いですよね。
[Jamstack公式サイト](https://jamstack.org/) の [Site Generatorsのページ](https://jamstack.org/generators/) には、
1クリックでNetlifyにデプロイできるボタンが並んでいてステキです。

GCP, AWS, AzureでもJambstackできるようになりました。
それぞれ、 [Firebase Hosting](https://firebase.google.com/docs/hosting?hl=ja), [AWS Amplify Hosting](https://aws.amazon.com/jp/amplify/hosting/), [Azure Static Web Apps](https://azure.microsoft.com/ja-jp/services/app-service/static/) です。

Firebaseは、GCPとの融合というよりも、独立環境を志向している感じです。
もともと独立したスタートアップ企業を、Googleが買収したものですしね。
アプリ開発に必要なもろもろを、ワンパッケージに閉じ込めようとしている印象を受けます。

Amplifyは、AWSの各サービスを横断的に、
かつ簡単に使えるようにすることを志向している感じです。
コミュニティドリブンであるのも特徴的ですし、
AWSの各サービスとの融合も良くできています。
一方、基本はCloudFormationテンプレートを生成するもので、
AWSの玄人による、玄人のための、便利ツール感がありますね。

さて、追いかけるAzureはどうするつもりなんだろう？というところですが、
未だにPREVIEW段階から抜けないのですが、
Azure Static Web Appsというサービスがあります。

PREVIEWですから、プロダクト環境では使えないですね。
本リリースされれば、Azureベースのシステムの場合に活用できるようになりそうなので、
さっそくAzure Static Web Appsを使い始めてみたいと思います。

# Static Web Appsを有効化する

今回は、新規にアカウントを開設したので、
有効化されているサービスがない状態からスタートです。

## リソースの作成

[ポータル](https://portal.azure.com/) から、リソースの作成を選びます。
![Screen Shot 2021-02-05 at 19.33.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/6b248b33-e6da-0f8f-b808-ff5d00bdf521.png)

`Static Web Apps` で検索するか、 `Web` カテゴリからたどります。
![Screen Shot 2021-02-05 at 19.40.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/160ffd1d-8a73-ecd6-fe32-ef597429d003.png)

## 静的 Web アプリの作成 〜 基本

[静的 Web アプリの作成](https://portal.azure.com/#create/Microsoft.StaticApp) 画面が開きます。
`リソース グループ` の作成が必須なので、新規に作成します。
![Screen Shot 2021-02-05 at 19.43.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/2cfbe920-42c8-0994-d4a3-02c0dfa5a7b1.png)

GitHubリポジトリからのデプロイが前提っぽいので、OAuth連携します。
![Screen Shot 2021-02-05 at 19.48.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/f920b798-4d2c-be60-395a-c979e9941b65.png)

どうやらリポジトリや `分岐`（ブランチのことですね）が選べるようになります。
そういえば、GitHubで新規にリポジトリを作ると、
[`master` ではなく `main` になるようになった](https://www.infoq.com/jp/news/2020/11/github-main-branch/) んですね。
![Screen Shot 2021-02-06 at 20.30.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/48056e14-01bb-4225-2ec9-7dcdad5d0dbc.png)

今回はNuxt.jsでビルドしてみたいので、 `ビルドのプリセット` を `Vue.js` にしてみました。
`API の場所` と `出力先` の指定が変わるようです。
基本的にGitHub Actionを使うみたいです。
さすがMicrosoft、買収したGitHubとのインテグレーションが進んでますね！
![Screen Shot 2021-02-06 at 20.31.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/9105686e-2456-c86d-915c-a4d69f2a25cd.png)

## 静的 Web アプリの作成 〜 タグ

`タグ` の設定は空っぽで良さそうです。
![Screen Shot 2021-02-06 at 14.46.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/93bed38b-2070-db28-4e90-7bd5851b6b25.png)

## 静的 Web アプリの作成 〜 確認および作成

デプロイ前の確認です。
実際には、Automationのテンプレートが生成されて、裏で実行されるようですね。
![Screen Shot 2021-02-06 at 14.48.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/e23be2b5-c9b7-ce5d-b19b-f8c386948041.png)

## デプロイが完了しました

しばらく待つと、デプロイが完了してリソースができあがります。
特徴的な画面ですねー
![Screen Shot 2021-02-06 at 14.52.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/c661293b-1d78-56ab-0351-e82219fb1563.png)

## 静的 Web アプリ リソース

`リソースに移動` ボタンをクリックすると、リソースの画面に遷移します。
日本語で表示されるので、優しいですね〜（きっと機械翻訳ですが）
![Screen Shot 2021-02-06 at 15.14.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/2f505d90-cb0f-1a24-aa4e-872e4609d3f8.png)

[デプロイされたURL](https://zealous-desert-0a47e4d00.azurestaticapps.net/) も表示されています。
アクセスしても空っぽなので、これからGitHubにコンテンツを準備していきます。
![Screen Shot 2021-02-06 at 15.16.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/32dfb011-b9b8-d826-391c-13da769554e8.png)

# デプロイ用コンテンツを作る

## Nuxtプロジェクトを作る

慣れているので、Vue.js + NuxtJSでコンテンツを作ってみることにします。
まずはサクッとローカルで `create-nuxt-app` で用意します。

```terminal
$ npx create-nuxt-app azure-static-web-apps-vue-helloworld

create-nuxt-app v3.4.0
✨  Generating Nuxt.js project in azure-static-web-apps-vue-helloworld
? Project name: azure-static-web-apps-vue-helloworld
? Programming language: JavaScript
? Package manager: Npm
? UI framework: None
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Static (Static/JAMStack hosting)
? Development tools: jsconfig.json (Recommended for VS Code if you're not using typescript)
? What is your GitHub username? kulikala
? Version control system: Git

🎉  Successfully created project azure-static-web-apps-vue-helloworld

  To get started:

	cd azure-static-web-apps-vue-helloworld
	npm run dev

  To build & start for production:

	cd azure-static-web-apps-vue-helloworld
	npm run build
	npm run start
```

## GitHubに接続する

GitHubのリポジトリには、すでに `.github/workflows` フォルダが登録されているので、
ローカルにクローンしてきて `.git` フォルダを入れ替えます。

```terminal
$ cd azure-static-web-apps-vue-helloworld/
azure-static-web-apps-vue-helloworld$ rm -rf .git
azure-static-web-apps-vue-helloworld$ git clone git@github.com:kulikala/azure-static-web-apps-vue-helloworld.git remote
Cloning into 'remote'...
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 8 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (8/8), done.
azure-static-web-apps-vue-helloworld$ mv remote/.git* .
azure-static-web-apps-vue-helloworld$ rm -rf remote/
```

## フォルダの整理をする

フォルダを整理して使いやすくします。

```terminal
azure-static-web-apps-vue-helloworld$ ll
total 1824
drwxr-xr-x   19 kulikala  staff   608B  2  6 14:44 ./
drwxr-xr-x   56 kulikala  staff   1.8K  2  6 14:44 ../
-rw-r--r--    1 kulikala  staff   207B  2  6 14:44 .editorconfig
drwxr-xr-x   10 kulikala  staff   320B  2  6 16:32 .git/
-rw-r--r--    1 kulikala  staff   1.2K  2  6 14:44 .gitignore
-rw-r--r--    1 kulikala  staff   374B  2  6 14:44 README.md
drwxr-xr-x    3 kulikala  staff    96B  2  6 14:44 assets/
drwxr-xr-x    4 kulikala  staff   128B  2  6 14:44 components/
-rw-r--r--    1 kulikala  staff   209B  2  6 14:44 jsconfig.json
drwxr-xr-x    4 kulikala  staff   128B  2  6 14:44 layouts/
drwxr-xr-x    3 kulikala  staff    96B  2  6 14:44 middleware/
drwxr-xr-x  736 kulikala  staff    23K  2  6 14:44 node_modules/
-rw-r--r--    1 kulikala  staff   1.0K  2  6 14:44 nuxt.config.js
-rw-r--r--    1 kulikala  staff   887K  2  6 14:44 package-lock.json
-rw-r--r--    1 kulikala  staff   316B  2  6 14:44 package.json
drwxr-xr-x    4 kulikala  staff   128B  2  6 14:44 pages/
drwxr-xr-x    3 kulikala  staff    96B  2  6 14:44 plugins/
drwxr-xr-x    4 kulikala  staff   128B  2  6 14:44 static/
drwxr-xr-x    3 kulikala  staff    96B  2  6 14:44 store/
azure-static-web-apps-vue-helloworld$ mkdir src
azure-static-web-apps-vue-helloworld$ mv assets/ components/ layouts/ middleware/ pages/ plugins/ static/ store/ src/
azure-static-web-apps-vue-helloworld$ ll
total 1824
drwxr-xr-x   12 kulikala  staff   384B  2  6 17:17 ./
drwxr-xr-x   56 kulikala  staff   1.8K  2  6 14:44 ../
-rw-r--r--    1 kulikala  staff   207B  2  6 14:44 .editorconfig
drwxr-xr-x   10 kulikala  staff   320B  2  6 17:09 .git/
-rw-r--r--    1 kulikala  staff   1.2K  2  6 14:44 .gitignore
-rw-r--r--    1 kulikala  staff   374B  2  6 14:44 README.md
-rw-r--r--    1 kulikala  staff   209B  2  6 14:44 jsconfig.json
drwxr-xr-x  736 kulikala  staff    23K  2  6 14:44 node_modules/
-rw-r--r--    1 kulikala  staff   1.0K  2  6 14:44 nuxt.config.js
-rw-r--r--    1 kulikala  staff   887K  2  6 14:44 package-lock.json
-rw-r--r--    1 kulikala  staff   316B  2  6 14:44 package.json
drwxr-xr-x   10 kulikala  staff   320B  2  6 17:17 src/
```

## ここまでをコミットする

まずはコミットしておきます。

```terminal
azure-static-web-apps-vue-helloworld$ git commit --amend --reset-author
[main dd93c7c] 🎉 Initial Nuxt project
 19 files changed, 22974 insertions(+), 2 deletions(-)
 create mode 100644 .editorconfig
 create mode 100644 .gitignore
 rewrite README.md (100%)
 create mode 100644 jsconfig.json
 create mode 100644 nuxt.config.js
 create mode 100644 package-lock.json
 create mode 100644 package.json
 create mode 100644 src/assets/README.md
 create mode 100644 src/components/Logo.vue
 create mode 100644 src/components/README.md
 create mode 100644 src/layouts/README.md
 create mode 100644 src/layouts/default.vue
 create mode 100644 src/middleware/README.md
 create mode 100644 src/pages/README.md
 create mode 100644 src/pages/index.vue
 create mode 100644 src/plugins/README.md
 create mode 100644 src/static/README.md
 create mode 100644 src/static/favicon.ico
 create mode 100644 src/store/README.md
```

## ディレクトリを設定する

```terminal
azure-static-web-apps-vue-helloworld$ vi nuxt.config.js
azure-static-web-apps-vue-helloworld$ vi jsconfig.json
azure-static-web-apps-vue-helloworld$ git diff
diff --git a/jsconfig.json b/jsconfig.json
index 29037a6..7dbac5d 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -2,8 +2,8 @@
   "compilerOptions": {
     "baseUrl": ".",
     "paths": {
-      "~/*": ["./*"],
-      "@/*": ["./*"],
+      "~/*": ["./src/*"],
+      "@/*": ["./src/*"],
       "~~/*": ["./*"],
       "@@/*": ["./*"]
     }
diff --git a/nuxt.config.js b/nuxt.config.js
index ca8ad10..fb782cc 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -1,4 +1,6 @@
 export default {
+  srcDir: 'src/',
+
   // Target (https://go.nuxtjs.dev/config-target)
   target: 'static',

azure-static-web-apps-vue-helloworld$ git add .
azure-static-web-apps-vue-helloworld$ git commit -m '🔧 fix NuxtJS configurations'
[main f4c0d98] 🔧 fix NuxtJS configurations
 2 files changed, 4 insertions(+), 2 deletions(-)
```

## それっぽいページを作成する

```terminal
azure-static-web-apps-vue-helloworld$ cp src/pages/index.vue src/pages/hello.vue
azure-static-web-apps-vue-helloworld$ vi src/pages/index.vue
azure-static-web-apps-vue-helloworld$ vi src/pages/hello.vue
azure-static-web-apps-vue-helloworld$ git add .
azure-static-web-apps-vue-helloworld$ git commit -m '🤡 add pages'
[main c8d2af6] 🤡 add pages
 2 files changed, 71 insertions(+), 6 deletions(-)
 create mode 100644 src/pages/hello.vue"
```

## GitHub Actionsを修正する

AzureのGitHub Actionsでの [カスタム ビルド コマンド](https://docs.microsoft.com/ja-jp/azure/static-web-apps/github-actions-workflow#build-and-deploy) のページを読んで、
ビルドに実行するnpmコマンドを `npm run generate` に変更します。

```terminal
azure-static-web-apps-vue-helloworld$ vi .github/workflows/azure-static-web-apps-zealous-desert-0a47e4d00.yml
azure-static-web-apps-vue-helloworld$ git diff
diff --git a/.github/workflows/azure-static-web-apps-zealous-desert-0a47e4d00.yml b/.github/workflows/azure-static-web-apps-zealous-desert-0a47e4d00.yml
index 3e606e3..b7e278b 100644
--- a/.github/workflows/azure-static-web-apps-zealous-desert-0a47e4d00.yml
+++ b/.github/workflows/azure-static-web-apps-zealous-desert-0a47e4d00.yml
@@ -29,7 +29,8 @@ jobs:
           # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
           app_location: "/" # App source code path
           api_location: "api" # Api source code path - optional
-          output_location: "public" # Built app content directory - optional
+          output_location: "dist" # Built app content directory - optional^M
+          app_build_command: "npm run generate"^M
           ###### End of Repository/Build Configurations ######

   close_pull_request_job:
azure-static-web-apps-vue-helloworld$ git add .
azure-static-web-apps-vue-helloworld$ git commit -m '👷 update GitHub Actions'
[main d9322f5] 👷 update GitHub Actions
 1 file changed, 2 insertions(+), 1 deletion(-)
```

## 404ページを設定する

[NuxtJSのデプロイ資料](https://nuxtjs.org/docs/2.x/deployment/deployment-azure-static-web-apps) を見ながら、404ページの設定を行います。

```json:src/static/routes.json
{
  "routes": [],
  "platformErrorOverrides": [
    {
      "errorType": "NotFound",
      "serve": "/200.html",
      "statusCode": 200
    }
  ]
}
```

# デプロイする

## GitHubにpushする

ここまで来れば、後はリポジトリにpushするだけです。

```terminal
azure-static-web-apps-vue-helloworld$ git push origin main
Enumerating objects: 55, done.
Counting objects: 100% (55/55), done.
Delta compression using up to 8 threads
Compressing objects: 100% (45/45), done.
Writing objects: 100% (50/50), 227.06 KiB | 1.40 MiB/s, done.
Total 50 (delta 14), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (14/14), completed with 1 local object.
To github.com:kulikala/azure-static-web-apps-vue-helloworld.git
   0b4f7b7..5aff04b  main -> main
```

## GitHub Actionsを待つ

後は自動的にGitHub Actionsが実行されて、デプロイまでが行われます。
![Screen Shot 2021-02-06 at 19.18.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/e3acd968-b811-da26-a1ce-5e802845951b.png)

## デプロイされる

デプロイが完了すると、 [サイト](https://zealous-desert-0a47e4d00.azurestaticapps.net/) が自動的に更新されます。
すごい楽ちんですね！
![Screen Shot 2021-02-06 at 19.20.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/aa1089be-782c-c489-15f6-b8e1a5d85814.png)

# Static Web Appsで複数環境のデプロイをする

Static Web Appsでは、運用環境とステージング環境が存在し、
pull requestに紐付くようになっているようです。

## 新しいブランチを作ってコミットする

では、新しいブランチ `feature/blue-page` を作って、ページを修正してみます。

```terminal
azure-static-web-apps-vue-helloworld$ git checkout -b feature/blue-page
<script>
Switched to a new branch 'feature/blue-page'
azure-static-web-apps-vue-helloworld$ vi src/layouts/default.vue
azure-static-web-apps-vue-helloworld$ vi src/pages/index.vue
azure-static-web-apps-vue-helloworld$ vi src/pages/hello.vue
azure-static-web-apps-vue-helloworld$ git add .
azure-static-web-apps-vue-helloworld$ git commit -m '💄 blue pages'
[feature/blue-page 734c2cb] 💄 blue pages
 3 files changed, 10 insertions(+), 6 deletions(-)
```

## GitHubにプッシュする

```terminal
azure-static-web-apps-vue-helloworld$ git push origin feature/blue-page
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 8 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 728 bytes | 728.00 KiB/s, done.
Total 8 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), completed with 5 local objects.
remote:
remote: Create a pull request for 'feature/blue-page' on GitHub by visiting:
remote:      https://github.com/kulikala/azure-static-web-apps-vue-helloworld/pull/new/feature/blue-page
remote:
To github.com:kulikala/azure-static-web-apps-vue-helloworld.git
 * [new branch]      feature/blue-page -> feature/blue-page
```

## GitHubでPull requestを作ってみる

新しいPull requestを作ると、GitHub Actionsが起動するようになっているようです。
![Screen Shot 2021-02-06 at 19.56.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/cfeab976-1d19-f7ba-1e6d-cdf28be7920e.png)

GitHub Actionsがデプロイを完了させるのを待ちます。
![Screen Shot 2021-02-06 at 20.06.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/a7cc789d-5296-d228-211d-db519c283254.png)

デプロイが正常に完了すると、Azure Static Web Appsのポータル上で、
`環境` ページの `ステージング` 欄にpull requestが表示されます。
![Screen Shot 2021-02-06 at 20.07.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/aaf0410a-af7e-49fd-968d-8e430fe8324a.png)

[デプロイされたページ](https://zealous-desert-0a47e4d00-1.eastasia.azurestaticapps.net/) は、ホスト名の部分に `-1` が追加されています。
この `1` は、pull requestのIDですね。
![Screen Shot 2021-02-06 at 20.09.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/b3e8ba6e-0620-3d2d-5573-893408dc0ae2.png)

# Static Web Appsをカスタム ドメインにする

自分が所有しているカスタム ドメインに向けることができるようですが、
`CNAME` でエイリアスを設定するようです。
![Screen Shot 2021-02-06 at 20.10.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/42b78909-5961-323b-4f85-d4f5b32fab5b.png)

画面的には複数設定できそうな感じですが、実際は運用環境を1つだけしか設定できません。
![Screen Shot 2021-02-06 at 20.15.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84173/48cf7a14-4437-ba28-c3f8-a68bb33f2043.png)

アクセスしてすぐには、TLS証明書のSubject Alt Namesにドメインが登録されないので、
セキュリティエラーが表示されます。
そのうちにSubject Nameにドメインが入った証明書が生成されるので、
無事にアクセスできるようになります。

# まとめ

`環境` の考え方や、`カスタム ドメイン`の扱い、`関数` という仕組みなど、
Azure Static Web Appsならではの癖がありますね。

とはいえ、GitHubとのインテグレーションは、他社よりも圧倒的に洗練されています。
今度は、 `関数` という仕組みを深掘りしつつ、Azureの他のリソースへのアクセス制御(IAM)や、
認証の実装方法について探索してみたいと思います。