SvelteKit + Azure StaticWebApps で SSR なサイトを構築する

# はじめに
Svelte4も出てきたし、[SvelteKit adapter for Azure Static Web Apps](https://github.com/geoffrich/svelte-adapter-azure-swa)なるアダプターもあるし、SveltKitでSSRなWebアプリをAzure StaticWebApps で構築するための手順をまとめました。

嵌ったのは以下の点です。特に2点目は途方にくれました。(Special Thanks to [Ota-san](https://qiita.com/okazuki))
- APIフォルダを、SSR用のAPIと同居させる設定
- Azure StaticWebApps のAPIをPOSTコールしたら404になる問題

というわけで以下が、これから試される方、いずれ忘れるであろう私向けの手順です。

# 1. 環境準備
久々のSWA開発だったので、ここでも[この問題](https://blog.azure.moe/2022/09/15/microsoft-azure-webjobs-extensions-http-%e3%81%a7%e3%82%a8%e3%83%a9%e3%83%bc%e3%81%ab%e3%81%aa%e3%81%a3%e3%81%a6functions%e3%81%8c%e8%b5%b7%e5%8b%95%e3%81%97%e3%81%aa%e3%81%84/)にはまりました。
[Microsoft .NET SDK](https://dotnet.microsoft.com/ja-jp/download)と、[Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools)は最新を入れなおしておいたほうがいいかもです。

あとはNodejs, gitとか、そのあたりも入ってる前提です

# 2. SveletKitのプロジェクト作成
[SvelteKitの公式手順](https://kit.svelte.jp/docs/creating-a-project)をもとにプロジェクトを作成します。
```bash
    npm create svelte@latest フォルダ名
    cd フォルダ名
    pnpm install
    
    # 動作確認
    npm run dev -- --open
```
これでSvelteの動作確認ができました。

# 3. Azure SWAのアダプターを設定する
このままだと、SSRができないので、[SvelteKit adapter for Azure Static Web Apps](https://github.com/geoffrich/svelte-adapter-azure-swa)の設定をいれていきます。また今回は自前のAPI（GET&POST)も追加したいので、その設定も必要です。

[公式の手順](https://github.com/geoffrich/svelte-adapter-azure-swa)をもとに実施します。
まずはadapterのインストールをします。
```
    pnpm install -D svelte-adapter-azure-swa 
```
これだけで動作確認するとモジュールが足りないと怒られるので(2023/7/6現在)、怒られたモジュールも追加します。
```
    pnpm install -D set-cookie-parser
```

あとは設定を追加します。`svelte.config.js`を以下のように書き換えます。
ポイントは以下です。
- 自前関数も用意したいので、apiDirに、'custom/api'フォルダを指定
- customStaticWebAppConfigに、apiへのルートを追加
  <font color="red">**※重要！この設定をいれないとSSR用のレンダーAPIにPOSTリクエストが吸収されてPOST呼び出しの時に、404が出ます！(PUTとかも必要あれば追加します）※**</font>
```javascript
//importするアダプターを変更
import azure from 'svelte-adapter-azure-swa';
/** @type {import('@sveltejs/kit').Config} */
const config = {
	kit: {
		adapter: azure({
            //大事な設定たちを追加
			apiDir: 'custom/api',
			customStaticWebAppConfig: {
				routes: [
					{
						"route": '/api/*',
						"methods": [
						  "GET",
						  "POST",
						],
					}
				],
			}
		})
	}
};
export default config;
```

あとは公式の通り、`src/app.d.ts`の1行目に以下を追加します。
```typescript
/// <reference types="svelte-adapter-azure-swa" />
```


ここで一回buildをかけます。
```
pnpm run build
```

そうするとcustom/apiフォルダにsk_renderなるものが追加されていると思います。このsk_renderのAPIがSSRの本体です。中はいじりません（いじれません）
なお、この時点だとFunction用の設定は入っていませんので、custom/apiフォルダの直下に`host.json`,`local.settings.json`,`package.json`を自分で追加します。
[アダプターの公式サイトにはここがガイド](https://github.com/geoffrich/svelte-adapter-azure-swa/tree/main/files/api)されてますが、今日時点だと以下ですね。

```:host.json
{
	"version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[3.*, 4.0.0)"
    }
}
```

```:local.settings.json
{
	"IsEncrypted": false,
	"Values": {
		"FUNCTIONS_WORKER_RUNTIME": "node"
	}
}
```

```:package.json
{}
```

先にfunc initしちゃってもいいですが。

# 4.自前の関数を追加する
custom/apiフォルダで、functionを追加します。
```bash
func new --template "Http Trigger" --name nankanomethod
```

custom/api/nankanomethod/index.jsをちょっとだけ変えてJSONを返すようにします（これは必須ではないです。JSONで受けたかったので変えただけです。ここ変えない場合は、後続のsvelteのテスト用のコードも変えてくださいね）
```javascript:index.js
module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    const name = (req.query.name || (req.body && req.body.name));
    const responseMessage = name
        ? "Hello, " + name + ". This HTTP triggered function executed successfully."
        : "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.";

    context.res = {
        status: 200, /* Defaults to 200 */
        body: {message: responseMessage},
        headers: {
            'Content-Type': 'application/json'
        }
    };
}
```


デフォルトだとGET,POSTに対応したnankanomethodができますね。一旦これでOKです。
あと折角追加したので、svelte側からコールできるようにしておきます。

`src/routes/+page.svelte`を編集します。以下、テキトーな例です。
```javascript
<script>
    /**
   * @type {string}
   */
    let message ="first status"

    async function callGET(){
        message ="calling GET"
        const response = await fetch('/api/nankanomethod')
        const data = await response.json()
        message = data.message
    }

    async function callPOST(){
        message ="calling POST"
        const response = await fetch("/api/nankanomethod", {
        method: "post",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({name:"Taro Yamada"})
        });

        const data = await response.json();
        message = data.message
    }
</script>

<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

<div>
{message}
</div>

<button on:click={callGET}>GET</button>
<button on:click={callPOST}>POST</button>
```

ここまででコードは完成です。

# 5. GitHubにコードをあげる
GitHubでリポジトリ―作ってから、initしてadd remoteして、addしてcommitしてpush します。

# 6. Azure StaticWebApps のリソースを作成する
AzureポータルでStaticWebAppsを作成します。以下の設定だけ注意してください。
|property|value|
|:--|:--|
|アプリの場所|./|
|APIの場所|custom/api|
|出力先|build/static|

あとは5.でつくったリポジトリ―を指定しておけばOKです。

# 7. 動作確認
GitHub Actionsが終わったら、Azure StaticWebAppsのサイトをみにいってきちんと動けば完成です。
お疲れ様でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/22ba5ba0-17e4-23e6-d3f0-6e9fed585a9c.png)

<br/>
<br/>
<br/>
<br/>
<br/>
<br/>





# 番外

開発環境向けにローカルでfunctionを実行してテストする場合は、プロキシー設定が必要です。'vite.config.js'にproxyを足します。
```javascript
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
	plugins: [sveltekit()],
    //server部分を追加
	server: {
		proxy: {
			'/api': {
				'target': 'http://127.0.0.1:7071',
				changeOrigin: true,
			},
		}
	},
});
```