GitHub PagesからAzure Static Web Appsへ引っ越しました


## 動機

これまで個人のウェブサイトをGitHub Pages上でJekyllを使って公開していました。

https://www.satoryu.com

ただ幾つか制約に感じることがありました。

1. Jekyllプラグインのうち一部しか使うことが出来ない
1. pull requestを送ってもプレビューが見れない
1. サーバーサイドで何か処理をしたくなった場合、別途自前で用意しなければならない

1つ目は、書いてあるそのままで、GitHub Pages上で使えるJekyllのプラグインはセキュリティの問題から下記のURLに記載されているものに限定されています。
以前から[linkpreview](https://github.com/ysk24ok/jekyll-linkpreview)というプラグインを使いたいと思っているのですが、この制約のためGitHub Pages上では利用できないでいました。

https://pages.github.com/versions/

2つ目は、修正したページやスクリプトなどを試す際には実際に本番環境としてデプロイするしか方法が無いということです。
NetlifyやVercelなどのように、pull requestのブランチごとに別ドメインとしてデプロイしてくれるような仕組みがあると便利です。

3つ目は、応募フォームやちょっとしたデータを受け取る、もしくはデータを取得するためのAPIのようなものが欲しい場合を指しています。
そういった時は、応募フォームであればSaaSのサービスを利用することで満たせる場合もありますが、カスタマイズが難しい場合もありますし、個別の要件を満たせない場合もありえます。
NetlifyやVercelであればそういったAPIサービスを構築する機能が既にあります。それらを利用すれば良いのですが、無料だと利用制限があったりします。

## そこでAzure Static Web Appsですよ

そうこう考えている中で、Azure Static Web Appsに自分は行き着きました。
その経緯について書きます。
前述の制約のうち1つ目は移行すればすぐに解決です。

2つ目の制約は、Azure Static Web Apps自体がそもそも機能として提供しています。
これも即時解決です。

https://docs.microsoft.com/ja-jp/azure/static-web-apps/review-publish-pull-requests

3つ目についても、APIをAzure Functionsの仕組みをそのまま利用して開発できる機能を持っているのでそれを利用すれば解決です。

https://docs.microsoft.com/ja-jp/azure/static-web-apps/add-api?tabs=vanilla-javascript

NetlifyにもFunctionsというAWS Lambdaを利用した仕組みが用意されています。AWS　Lambdaに慣れているようであればそちらも十分な選択肢かと思います。ここは個人的にAzure Functionsの方が使い慣れているという点で選んでいます。また、同じAzureサブスクリプション上ですでに作成した他のAzureのリソースとの連携ができ、当然のことながら他のAzureリソースと同じようにAzure Static Web Appsも扱えるので管理が楽になったと感じています。課金もAzureのサブスクリプションにまとめられる[^1]のも利点であると考えています。

[^1]: 今のところ無料プランで稼働させています。

といった制約が乗り越えられたので、Azure Static Web Appsへの移行を決めました。

## 引っ越す :package:

これは非常に簡単です。下記のドキュメントにある手順通りで、

https://docs.microsoft.com/ja-jp/azure/static-web-apps/publish-jekyll

既にGitHub Pagesのリポジトリは持っていたので、この手順の前半は省略できました。

カスタムドメインの切り替えなど、別途行いましたが、利用しているDNSのCNAMEレコードを更新すれば完了です[^2]。

https://docs.microsoft.com/ja-jp/azure/static-web-apps/custom-domain?tabs=azure-dns

[^2]: 自分はサブドメインを割り当てたのでCNAMEレコードを利用しましたが、Apexドメインを利用することも可能です。

以上。これだけ。

## ローカルでの開発環境を作る :gear:

Static Web Appsをローカルの環境で再現するためのCLIがあるので、それを利用してデプロイ前に動作を確認できるようにします。

https://github.com/Azure/static-web-apps-cli

まずはCLIをインストールします。

```sh
$ cd /path/to/your/jekyll-site
$ npm init --yes
$ npm install -D  @azure/static-web-apps-cli
```

インストールしたパッケージをGitの管理に混ぜないように`.gitignore`を更新します。

```:.gitignore
node_modules/
```

同様に、Jekyllがビルドの対象にしないように、`_config.yml`に`exclude`オプションを追加します。

```_config.yml
exclude:
   - api/
   - node_modules/
   - Gemfile*
   - package*.json
```

`exclude`オプションを指定すると、それまでデフォルトで除外していたような`Gemfile`なども指定する必要があるようです。

それでは起動してみます。

```sh
$ npx swa start http://localhost:4000 --run 'bundle exec jekyll serve'
```

これによって、

- `--run`オプションんにより、Jekyllの開発用のWebサーバー（デフォルトで`http://localhost:4000`）が起動
- `start http://localhost:4000` で、立ち上がった開発サーバーにプロキシとして接続

ということを指定しています。
また別の方法として、swa自体がウェブサーバーとしても利用できるので、Jekyllはビルドのみしてもらうことも出来ます。

```sh
$ npx swa start _site --run 'bundle exec jekyll build -w'
```

`jekyll build` につけた`-w`オプションは、ファイルの変更を監視（`watch`）を指定しています。これにより、ファイルが追加されたり変更されるとビルドが自動的に実行されます。

これでローカルで確認できるようになったので、ブラウザで`http://localhost:4280/` にアクセスすると、Jekyllでビルドしたコンテンツが参照できるようになっているはずです。

また、例えば404の応答を独自のHTMLに変更した場合の確認などができるようになります。

https://docs.microsoft.com/ja-jp/azure/static-web-apps/configuration#response-overrides

## APIを作ってみる :computer: 

APIを試すために、問合せフォームを作ってみました。実際にやってみたのがこちらのpull requestです。

https://github.com/satoryu/satoryu.com/pull/3

以下、そのためにやった手順を書きます。

まず、Visual Studio CodeのAzure Static Web Appsという拡張をインストールします。

https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestaticwebapps

この拡張は、Azure上のAzure Static Web Appsリソースを操作したり、APIのためのFunctionを生成したりできます。

インストールできたら、実際にAPIのためのFunctionを生成します。
コマンドパレット（Macであれば`Cmd+Shift+P`）を開き、`Azure Static Web Apps: Create HTTP Function...`を選択し、Function名を入力し、好きなプログラミング言語を選択して、雛形を生成します。
例えば、JavaScriptで`PostInquiry`という名前を指定したら、`api/PostInquiry`というディレクトリが生成され、そのディレクトリの中に、`function.json`や`index.js`というファイルが生成されます。
これは完全にAzure Functionsと同じです。しかしながら1つ大きな制限があり、Azure Static Web AppsのAPIではHTTPトリガーしか使えないという点です。
この制限が厳しい場合は、別途Azure Functionsリソースを生成して、それをAzure Static Web Appsで利用するという方法があるようです（未確認）。

https://docs.microsoft.com/ja-jp/azure/static-web-apps/functions-bring-your-own

このAPIをローカルで起動できるようにするための環境を作っていきます。これもまたAzure Functionsをローカルで開発するときと全く同じ手順です。

```sh
$ npm install -g azure-functions-core-tools@3
```

もしくは、Macを利用している人であればHomebrewでもインストールできます。

```sh
$ brew install azure/functions/azure-functions-core-tools@3
```

これで起動するための準備ができたので、`swa`コマンドを使って起動する。

```sh
$ npx swa start _site --run 'bundle exec jekyll build -w' --api ./api
```

先に実行した際のコマンドに`--api`オプションが追加されている。このオプションにより、APIがどのディレクトリにあるのかを指定しています。

ここで起動できなかった場合、原因の1つとしてサポートされているNode.jsのバージョンではない可能性があります。
Azure Functionsではv14系がサポートされているバージョンで最も最新のものです（2021/8/25時点）。
例えば、v16系がインストールされている環境で`swa`コマンドで起動しようとすると、下記のようなエラーがログに出てきます。

```text
[api] Azure Functions Core Tools
[api] Core Tools Version:       3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d  (64-bit)
[api] Function Runtime Version: 3.0.15584.0
[api] 
[swa] - Waiting for http://localhost:7071 to be ready
[api] [2021-08-25T08:07:49.409Z] Cannot create directory for shared memory usage: /dev/shm/AzureFunctions
[api] [2021-08-25T08:07:49.409Z] System.IO.FileSystem: Access to the path '/dev/shm/AzureFunctions' is denied. Operation not permitted.
[run] Configuration file: /Users/satoutatsuya/Projects/private/satoryu.com/_config.yml
[run]       Remote Theme: Using theme mmistakes/minimal-mistakes
[api] [2021-08-25T08:07:50.426Z] /usr/local/Cellar/azure-functions-core-tools@3/3.0.3477/workers/node/dist/src/nodejsWorker.js:35
[api] [2021-08-25T08:07:50.426Z]         throw message;
[api] [2021-08-25T08:07:50.426Z]         ^
[api] [2021-08-25T08:07:50.426Z] Incompatible Node.js version (v16.7.0). Refer to our documentation to see the Node.js versions supported by each version of Azure Functions: https://aka.ms/functions-node-versions
[api] [2021-08-25T08:07:50.426Z] (Use `node --trace-uncaught ...` to show where the exception was thrown)
```

起動できた場合、ブラウザで`http://localhost:4280/api/PostInquiry`にブラウザからアクセスしてみると何かしらレスポンスがあるはずです。

あとは実際にAPIを実装します。
APIのコードを実装し、保存すると、そのタイミングでAPI（Azure Functions Core Toolsで起動されたプロセス）が再起動されます。

## いざデプロイ :rocket:

デプロイした際のビルドは、`npm run-script build` または`npm run-script build:azure` で行われます。
どうやら`package.json`が追加されると`node`のアプリケーションとして判断されるようで、`package.json`内に`scripts.build`または`scripts.build:azure`が定義されている必要があります。

またGitHub Actionsのビルドの設定ファイル`.github/workflows/azure-static-web-apps-nice-field-0cd594e00.yml`（拡張子手前のランダムな文字列はアプリケーションによって異なります）の中にある`api_location`でAPIのコードがあるディレクトリを指定します。

```.github/workflows/azure-static-web-apps-nice-field-0cd594e00.yml
   api_location: "./api"
```

ここまで出来たら`git commmit`でコミットを作成し、そのブランチを`git push`でGitHub上に上げましょう。
`master`ブランチであれば、そのままGitHub Actionsが実行されて、ビルドとデプロイが実行されます。
もし作業用のブランチにコミットしたのであればpull requestを作成すると、実稼働前環境へデプロイされます。
実稼働前環境は、別ドメインでなおかつ環境変数は本番環境と同じものが使用できます。DBなどを共用してしまうと本番環境に影響が出てしまう可能性があるので、多少注意が必要です。

## おわりに

ということで、今回実施したJekyllで生成したサイトをGitHub PagesからAzure Static Web Appsへ引っ越しして、問合せフォームをAPI（Azure Functions）で作ったことについて書きました。
Netlifyなど他の静的コンテンツを配信するサービスのように使いやすく、GitHub　Actionsでビルドとデプロイができるので既存の仕組みの組み合わせのため学習コストが低いように思いました[^4]。
個人のウェブサイトのようなものであれば、無料プランでも十分使えるものだと思います。

今回はまだ手を付けていませんが、APIから他のAzureのサービスと連携させることもできるでしょう。
例えば、問合せを受けたらAzure Queue Storageへキューイングし、それをトリガーにLogic AppsでExcelに問合せ内容を記録するといったこともできると思います。
それらの連携が扱いやすそうなのもAzure Static Web Appsの良いところだと思います。

ということで、Azureのアカウントを持っていて、個人のウェブサイトを作ろうとするのであれば、Azure Static Web Appsは十分な選択肢だと思いました。

[^4]: もともとAzure Functionsを使っていたことでコストが低かったのかもしれませんが。