React を Azure Static Web Appsでホスティングする開発・本番環境を作る

[Azure Static Web Apps](https://docs.microsoft.com/ja-jp/azure/static-web-apps/) という SPA 用の Azure リソースがあります。React や Vue.js などを実に簡単にホスティングできます。
2020/12時点でまだプレビューで、機能的にはまだこれからなところがありますが、これから間違いなく主役級リソースとなるでしょう。
本記事では React を使った ローカル開発環境の構築と、Azureへのデプロイまでの手順を記載します。

## 事前準備

以下の準備が必要です。

- [Node.jsのインストール](https://nodejs.org/ja/)
- [Yarnのインストール](https://classic.yarnpkg.com/ja/)
    - npmと同じパッケージリポジトリを使用するNode.jsのパッケージ管理ツールです。必須ではありませんが、個人的に使用を推奨します。
- [Visual Studio Codeのインストール](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)
- [Visual Studio Code Azure Functions 拡張機能](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
Azure FunctionsをC#で実装する場合は[.NET Core 3.1](https://dotnet.microsoft.com/download/dotnet-core/3.1)をインストールします。2020/12時点で、.NET 5はまだ対応していません。
- [Azure Functions Core Tools](https://classic.yarnpkg.com/ja/package/azure-functions-core-tools)
    - npm のパッケージです。グローバルにインストールしますが、あらかじめインストールしなくてもデバッグ実行時にインストールを求められますので、その時にインストールしてください。Windows環境の方で自分でインストールしたい場合はx64版をインストールしてください。リンク先の熟読をお願いします。
- [Gitのインストール](https://git-scm.com/)
- [GitHub アカウント](https://github.com/)
    - アカウントがない場合はこれを機会に作成をお願いします。無料で使用可能です。

## GitHub にリポジトリを準備し、ローカルリポジトリと同期

まずは GitHub にリポジトリを作りましょう。myappという名前で私は作ります。publicかprivateかはどちらでもOKです。.gitignoreファイルは作らないでおきましょう。この後の React アプリ作成時に自動的に作成されるため、重複するからです。

<img width=700 alt="create repository" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/21ff656a-9990-532a-24d6-c40390cb695f.png" />

Powershell、コマンドプロンプト、bashなどのターミナルからを立ち上げてください。これから GitHub に作った Repository をローカルに clone しますので、ソースを配置したい場所へ移動します。私はソースは常に C:\gitroot 配下に作ることにしていますので、そこに移動します。
clone コマンドは Repository 名のフォルダを自動的に作成しますので、C:\gitroot で下記コマンドを叩きます。\<org_name\>の箇所は、自分のアカウント名か、組織名（GitHub内の組織）に書き換えてください。

```powershell
git clone https://github.com/<org_name>/myapp.git
```

自動作成したフォルダに移動してから、お作法として、最初のコミットには空のコミットを入れておきます。ついでに、もし branch 名が master なら main にリネームしておきます。（リネームはしなくてもいいのですが、最近の流れはmasterというブランチ名を使わないそうです）

```powershell
cd myapp
git commit --allow-empty -m "first commit"
git branch -M main
```

無事に変更出来たら、push します。master を main に変更していますので、わざわざ origin へ HEAD を pushする、と指定しなければなりません。

```powershell
git push origin HEAD
```

最初の push はこれでいいのですが、毎回 origin HEAD を指定するのは面倒です。今後のために up-stream 先の master を mainに変更しておきます。

```powershell
git branch --unset-upstream
git branch --set-upstream-to origin/main main
```

これで以降の push は次のコマンドで簡単になります。

```powershell
git push
```

## Reactアプリを作成する

自分で１つずつ組み上げてもいいですし、create-react-appを使ってもいいので、Reactアプリを作ります。
create-react-appで作らない場合や既存のReactアプリの場合は、package.json の scripts に build コマンドを作って本番用にビルドした結果をどこかのフォルダに固めるようにしておいてください。後で構成する GitHub Actionsは "yarn run build" を叩く仕様になっているからです。
今回はmyappという名前のReactアプリをcreate-react-appを使ってTypeScriptで作ります。
注意がいるのは、yarn create コマンドもフォルダを自動作成しようとします。なので、このままだと1階層多くなってしまうので、一つ上の階層（C:\gitroot）に移動してからyarn create します。

```powershell
cd ..\
yarn create react-app myapp --template typescript
```

動かしてみましょう。

```powershell
cd myapp
yarn start
```

自動的にブラウザが立ち上がり、画面が表示されればOKです。
<img width="300 alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/5ee3dcfd-eb58-4f4f-82ed-e7a89e7a4141.png">

CTRL+Cを押下して、yarn を停止しましょう。今後はこのターミナルで React アプリを起動はしません。

## Visual Studio Codeでフォルダを開く

yarn start したターミナルが残っている場合は、そこで次のコマンドを叩けばそのフォルダを VSCode で開くことができます。

```powershell
code . -r
```

もしくは、エクスプローラーで右クリック→「codeで開く」とするか、 VSCode を起動してから「ファイル」→「フォルダーを開く」を選択してからgit cloneして自動作成されたフォルダを選びます。

以降の作業は VSCode のターミナルを使用します。「ターミナル」→「新しいターミナル」を選択します。

<img width=700 alt="vscode terminal open" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/6444f9c5-88d7-f15a-eaef-2c82aa1ed2f4.png" />

こんな画面が表示されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/8a20634d-682b-5f3a-1eeb-301e1b8e63ab.png)

powershell だったり、bash だったりと表示されるターミナルは環境によって様々のはずです。私の場合は Windows で Powershell を使用していきます。
ちなみに私の Powershell ターミナルにカラフルな情報（現在位置と Git 情報）が表示されているのは、別途そういうセットアップをしているからです。ご興味がある方は[こちら](https://docs.microsoft.com/ja-jp/windows/terminal/tutorials/powerline-setup)をどうぞ。

## Azure Functions アプリをローカルで作る

一番上の階層にapiフォルダを作ります。ここにAzure Functionsアプリを構成します。（ちなみにapiという名前じゃなくても本当は大丈夫です。後ほどAzure Static Web Apps をAzure上に新規作成するときにAzure Functionsの場所を指定するので、api以外の名前を付けた場合はそれを指定できるからです）

<img width=400 alt="create api folder on the top" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/292f85ae-5b5e-2c60-763e-84c5ed44f88b.png" />

Azure Functions の作成はAzure Functions拡張機能を使います。Visual Studio Codeの「表示」メニューからコマンドパレットを開きます。
<img width=600 alt="show command pallet" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/1d4a6478-ec57-cbd9-910a-01a7ce4cc021.png" />

Azure Functions:Create New Projectsを一文字ずつ入力していくと選択肢が絞られていきます。選択します。

<img width=350 alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/186888ab-bbb2-718a-d1ad-0ec01a09b94a.png" />

選択すると、情報入力をウィザード形式で求めてきます。最初は Azure Functions を作る場所です。apiフォルダを指定したいので、Broseを選択してから api フォルダを選択します。

<img width=700 alt="select api folder" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/f50ab241-f249-2329-3fd7-23d0dd01396e.png" />

どの言語で実装するのかを選択します。今回はC#で作ります。

<img width=700 alt="select lang" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/cd1b95b8-6b99-0306-37d1-f0c59626fbe0.png" />

次はAzure Functions のトリガーを選択します。選択肢のドロップダウンが表示されるまで5秒ぐらいかかりますので少し待ちましょう。
HttpTriggerを選択します。

<img width=700 alt="select httptrigger" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/276c212b-2d67-13a8-e74d-75c2d3ca13f8.png" />

最初に作成するメソッド名を選択します。APIのURLでもあります。デフォルトで名前を提案されますが、「GetData」変えます。
メソッド名は後から変更できます。

<img width=700 alt="input function name" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/31ab496e-0673-0ff2-9d68-eeb88eb00f51.png" />

名前空間を決めます。これも後から変更可能です。今回はデフォルトのままにします。
<img width=700 alt="input namespace" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/b9fd4293-2945-ab5b-8495-108c05fe5504.png" />

最後に、APIアクセス時の認証設定です。本番時には認証を入れるべきですが、今回は「認証なし」を意味するAnonymousを選びます。
<img width=700 alt="select auth" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/e850cef5-c4cc-7a7f-1d74-825f50b576e4.png" />

Azure Functions が作成されるとエクスプローラーはこのようになります。

<img width=450 alt="folder structure after azure functions created" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/6cc91801-e80b-09e9-dee5-48a9b8de0134.png" />

エラーが大量に出ていると思いますが、パッケージをダウンロードしてないからです。気にせずデバッグ実行してみましょう。（デバッグ実行のプロセスの最中にパッケージもダウンロードします）
左のデバッグ実行メニューをクリックしてから、実行ボタンをクリックします。

<img width="350" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/d61ec590-d0c0-2176-f9a5-48f54980aebe.png" />

もしかしたら、この時に Azure Functions Core Toolsのインストールを求めるポップアップが上がってくるかもしれません。その場合はインストールしてください。

<img width=650 alt="install azure functions core tools" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/3194c188-e795-4bd0-b725-1093ac0987dc.png" />


無事に起動すると、URLがターミナルに表示されます。ブラウザで開きましょう。（CTRL＋クリックで開きます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/7530b6aa-04c7-ff98-810f-4645efde4dc0.png)

エラーなくメッセージが表示されればOKです。

<img width=700 alt="view azure functions in browser" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/d370e274-0ebe-9835-9ee0-901a040953de.png" />

この後すぐにソースを修正するので、デバッグ実行は停止しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/deccda9e-491f-a942-5710-9846d1df25bf.png)


## JSONを返すようにAPIを実装する

ダミーのJSONを返すように実装します。

```c#
namespace Company.Function
{
    public static class GetData
    {
        [FunctionName("GetData")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {

            var results = new Data[] {
                new Data { Id = 1, Name = "Sato" },
                new Data { Id = 2, Name = "Suzuki" }
             };

            return new JsonResult(results);
        }
    }

    public class Data {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
```

asyncメソッドなのにawaitがないから同期的に実行しますよ、という警告が出ると思いますが無視します。今回そこはどうでもよいところです。
忘れずにデバッグ実行を開始しておきましょう。

## APIを呼び出すように React を実装する

外部へのhttpアクセスにはjavascriptのfetchメソッドを使うことができますが、httpステータスエラーを補足しない、という特徴がありますので私は使いません。いつもaxiosを使いますので今回もそうします。VSCode のターミナルで次のコマンドを叩きます。

```powershell
yarn add axios
```

App.tsxファイルを以下のように修正します。Azure Functions からデータを取ってきて、それを表示しているだけです。

```tsx:App.tsx
import React from 'react';
import axios, { AxiosResponse } from 'axios';

function App() {

  const [response, setResponse] = React.useState<AxiosResponse>();

  React.useEffect(() => {
    axios.get('http://localhost:7071/api/GetData')
    .then(response => {
      // handle success
      setResponse(response);
      console.log(response);
    })
    .catch(error => {
      // handle error
      console.log(error);
    })
    .then(() => {
      // always executed
    });
    }, []);

  return (
    <React.Fragment>
      {response?.data.map((elm:any, index:number) => {
        return (
          <div key={index}>id: {elm.id}, name: {elm.name}</div>
        )
      })}
    </React.Fragment>
  );
}

export default App;

```

yarn start で実行してみましょう。（デバッグ実行が表示されていてコマンドが叩けませんか？その場合はターミナル右側に表示されている「2:タスク-host start」をクリックしてみてください。「1:powershell」というのがありませんか？それを選択します）

<img width=300 alt="switch terminal" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/15358911-0a8b-ed85-b574-c534fea86751.png" />

・・・ブラウザには何も表示されないですね。開発者ツールから、Consoleを開いてみます。
<img width="90%" alt="error.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/e4f21ec3-f779-1662-9b21-55781029fb63.png" />

CORS policyにブロックされた、とメッセージが出ています。

## ローカル開発用のCORSを設定する

デフォルトの状態では Reactは http://localhost:3000 で Azure Functions は http://localhost:7071 です。ホストは同じですがポートが異なるため、CORS(Cross-Origin Resource Sharing)ではオリジンが異なると判定されてしまい、APIアクセスの結果がエラーとなります。これを解決する方法は二つあって、一つはアクセスされる側であるAzure Functionsで明示的にReact側のオリジンを許可するように設定するか、React側で外部アクセスする時にProxy機能を使ってアクセスするホストを書き換えるかのどちらかになります。

Azure Functions でReact側のオリジンを設定する方法ですが、なぜか SignalR のdocsに設定方法が記載されています。
https://docs.microsoft.com/ja-jp/azure/azure-signalr/signalr-concept-serverless-development-config#enabling-cors

しかし、この方法は何かしら特別な理由がない限りは採用しないでしょう。APIアクセス時のURLを何かしらの方法（環境ファイルなど）を使って切り替えなければいけないですし、デプロイ後の本番環境（Azure Static Web）ではReactアプリもAPIサーバーと同じオリジンになるように構成されるため、余計なCORS設定は削除しておく必要があります。積極的に採用する理由がありません。

通常はReactアプリ側でProxy機能を利用するでしょう。create-react-appで作成した場合はpackage.jsonの一番上の階層（nameやversion等と同じ階層）に次のように実装します。

```json
  "proxy": "http://localhost:7071"
```

create-react-appを使用していない場合や既存のReactアプリの場合は使用しているWebサーバーの仕様次第ですので、ご自身で設定をお願いします。（webpack-dev-serverを使っている方が多いと思います。その場合はwebpack.config.jsのdevServerセクションにproxy設定をすることができます。[公式の解説](https://webpack.js.org/configuration/dev-server/#devserverproxy)）

Proxy機能を使う場合、APIアクセス時のURLを書き換える必要があります。

```tsx
    axios.get('http://localhost:7071/api/GetData') // 修正前
    axios.get('/api/GetData') // 修正後
```

proxy機能を有効にするために、Reactを実行中の場合はCTRL+C押下で一度停止して、再度yarn startで開始してください。
ブラウザで http://localhost:3000 にアクセスします。このようになれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/2779b055-d75e-05a8-8d0e-33569f7b2a98.png)

これでローカルの開発環境作成は完成です。Azure Functions のソースにブレークポイントを置いてからブラウザを再読み込みすると、ブレークポイントで止まります。確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/7be267ac-6e74-4b45-7c2d-3ac014a111a8.png)

## Azure Static Web Appsを作成する

[Azureのポータル画面](https://portal.azure.com)を開き、リソースの作成をクリックします。

<img width=250 alt="create new" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/4e1e3d9e-6d74-c508-d4c0-fa98d4a51a14.png" />

検索ボックスに static web apps を入力していくと、Static Web Appが絞り込みで表示されてきますので、選択します。

<img width=700 alt="search static web app" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/b6e3453b-5616-8d40-1318-62b186d8673b.png" />

Static Web App の作成画面が表示されます。作成をクリックします。

<img width=600 alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/9a4ea440-dfd7-12bb-877d-5f840e57ddb0.png" />

情報を入力します。

- リソースグループは新規で作成します。（既存のグループを使ってもいいですが）今回はstaticwebtestという名前にしました。
- 静的 Webアプリの名前を適当に付けます。今回はmystaticwebにしました。
- 地域は選択します。今回は East Asiaにしました。2020/12現在、日本リージョンはまだ使用できません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/6caf3cb7-7afe-32fb-64a5-e6fd6d62eca0.png)

「GitHubアカウントでサインイン」をクリックすると、画面がポップアップします。「Authorize Azure-App-Service-Static-Web-Apps」をクリックします。

<img width=600 alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/0924a4d7-b85d-6851-5fa6-ac0f535988d4.png" />

パスワードを入力し、「Confirm password」をクリックします。

<img width=400 alt="confirm github password" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/44b7aaab-eee9-9d85-6ed1-c7081a795534.png" />

GitHubアカウントから、デプロイに使用するリポジトリとブランチ（分岐）を選択します。今回はmainブランチを選択します。
「ビルドの詳細」では、まずビルドのプロセットを選択します。今回はReactを選びます。すると、自動的に

- アプリの場所
- APIの場所
- アプリの成果物の場所

の3つに値が入力されます。ここでいうアプリ、とはReactアプリのことを指します。次のように自動的に値が入ります。

| アプリの場所 | / |
|---|---|
| APIの場所 | api |
| アプリの成果物の場所 | build |

<img width=700 alt="confirm react app settings" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/e1d2d87c-ba93-5a22-aa86-809c61b468c0.png" />

左下の「確認及び作成」をクリックします。

<img width=700 alt="confirm to create static web app" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/90387d22-6084-5b9c-64d7-37fb1c1b417e.png" />

作成をクリックします。デプロイが開始されます。デプロイが完了すると「リソースへ移動」が表示されますのでクリックします。

<img width=500 alt="static web app created" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/4fa4dd4d-6e19-d72c-5619-974f233726e9.png" />

## GitHubへソースを push して deploy する

いよいよ Azure Static Web App にデプロイするわけですが、それにはソースを GitHub へ Push することが相当します。
最初に GitHub のリポジトリに空のコミットを行いました。以降は何もコミットしていませんが、実はさきほどAzure Static Web App を作成したときにファイルが1つ追加されてコミットされています。そのため、今の時点ではリモートリポジトリ（GitHub のリポジトリ）の方が先に進んでしまっているので、まずはリモートリポジトリと同期することから始めます。
React アプリと Azure Functions のデバッグ実行を停止したら、次のコマンドを叩きます。（私は普段 git コマンドを使いますのでコマンドを紹介しますが、せっかくVSCodeを使っているのでGUIを使って作業しても構いません）

```powershell
git pull
```

成功したら、すべてのソースをコミットしてから push します。

```powershell
git add .
git commit -m "first deploy"
git push
```

git push に成功すると、自動的にGitHub Actionsはソースのビルドを行い、デプロイ用にZipファイルを作成し、アップロードし、デプロイ先がそのZipを展開します。そんなGitHub Actionsでのデプロイ状況を見る場合は GitHub の Actions を開いて、履歴を参照します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/ee3f789d-cca4-bcae-5b9f-a96693f7c5b0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/87afad8b-63a8-654b-a1ff-36c3f140aa85.png)

「Build And Deploy」をクリックして開くと、デプロイ処理の詳細を見ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/1aaf3784-0464-37df-f6b5-bd7e15c79105.png)

Nodeのインストールから始まり、yarn install でパッケージ復元をして・・・という一連の流れを見ることできます。一度は見ておいたほういいでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/6e6e68b5-4910-2408-ee94-42f33914d048.png)

## 確認
Azure Static Web Appの概要を開くと、右側にURLが表示されています。クリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/ec78b0a9-4ac0-d920-43d7-b34bd8c32f7c.png)

Azure Static Web AppでReactアプリがホスティングされました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/284781ac-c130-c668-fb52-5916562882f8.png)

途中のセットアップで気づかれたかと思いますが、APIの場所は別に api フォルダじゃなくてもいいし、Reactアプリをビルドした結果をまとめる先がbuildフォルダでなくても問題ありません。Azure Static Web App作成時に指定した場所を変更したい場合は GitHub Actions のワークフローを修正します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/3fb4eef7-771f-0605-af0a-5d42b1e49863.png)

いかがだったでしょうか。実は一番ややこしいのは git の操作なのではないか、と思います。慣れていないと難しいですね。
まだ日本リージョンではホスティングすることができませんが、近い将来には日本リージョンでも動かせる日がきっと来ると思います。

是非、React アプリを Azure Static Web App で動かしてみてください。とても便利ですよ。