AWS AmplifyでSvelteKitアプリケーションをデプロイする

## はじめに

- AWS Amplifyはあまり触ってこなかったけど、Gen2で色々いい感じになったらしい
- 推しフレームワークSvelteKitをGen2で動かして使用感を試してみる

## SvelteKitとわたし

https://kit.svelte.dev/

- SvelteKitは、WebフロントエンドフレームワークSvelteをベースとしたフルスタックアプリケーションのためのフレームワークです
- 筆者はSvelteKitを、Cloudflare Pages、AWS Lambda、AWS Lambda + Web Adapterでそれぞれ運用していて、それぞれのうれしみ・つらみがなんとなくわかってきたところです

## AmplifyにSvelteKitをデプロイする

https://docs.aws.amazon.com/amplify/latest/userguide/sveltekit-support.html

公式ドキュメントがあり、`amplify-adapter`を利用すると良さそうです。ただし、

:::note info
SvelteKitは「Adapter」という仕組みを利用して、同一コードから複数の環境へデプロイすることを可能としています。オフィシャルにメンテナンスされているアダプターのほか、サードパーティによるアダプターもあります。`amplify-adapter`は後者です。
:::

### 手順

https://docs.aws.amazon.com/amplify/latest/userguide/get-started-sveltekit.html

上記ページのとおりで良く、要約すると以下です。

1. SvelteKitアプリケーションを作成する
2. `amplify-adapter`をインストールし、デフォルトと差し替える
3. ソースコードをGitHubへアップロードする
4. AWS Amplifyコンソールでプロジェクトを作成する
5. 3のリポジトリを選択する
6. ビルド設定で、ビルド先ディレクトリとして`build`を指定する

```sh
# コマンド例
npm create svelte@latest sveltekit-amplify
npm install -D amplify-adapter
vi svelte.config.js # adapterを差し替え

git init
git add .
git commit -m init
git push origin main
# 以降はAWS Amplifyコンソールでの操作
```

:::note
上記ページでは
> In the amplify.yml file, locate the frontend build commands section. Enter - cd build/compute/default/ and - npm i --production.

と説明されています。しかしデフォルトの状態では`build/compute/default/package.json`に`dependencies`が記述されているわけでもないので、この設定をせずとも動作しました。ビルド時にminify出来ない依存関係が含まれている場合は、この設定が必要となります（例：バイナリを含むライブラリなど）。
:::

ここまででAmplifyによりSvelteKitアプリケーションがデプロイされ、正常に動作するはずです。

![Screenshot 2024-09-21 at 11.30.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/6b5389e2-981f-806e-8d65-ef35c076c6a2.png)

## デプロイプレビュー

![Screenshot 2024-09-21 at 12.21.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/34b6d198-1844-8187-8ffa-5a31ba798adf.png)

- プライベートリポジトリであり、かつPR先リポジトリのプレビューが、コンソール上で有効化されていれば、PR作成時にビルドが走り、プレビューページのURLを取得できる

## API routes

```typescript: src/routes/api/hello/+server.ts
import type { RequestHandler } from '@sveltejs/kit';

const getHandler: RequestHandler = async (request) => {
    return new Response('Hello from the server!');
};

const postHandler: RequestHandler = async (request) => {
    return new Response('Hello from the server! post');
};

export const GET = getHandler;
export const POST = postHandler;
```

SvelteKitのお作法でAPIエンドポイントを実装しておく

```sh
curl https://path/to/app/api/hello
Hello from the server!

curl -X POST https://path/to/app/api/hello
Hello from the server! post
```

問題なく動作する。

## SSRとStreamingをテストする

SvelteKitはStreaming、ベースのHTMLを先にレスポンスし、動的コンテンツはサーバー内の読み込みが完了した段階で後から送るという機能に対応しています。`+page.server.ts`でデータフェッチングを記述することでSSRを実装することができます。今回は下記のコードを利用してみます。

```typescript:+page.server.ts
import type { PageServerLoad } from './$types';

type Todo = {
    userId: number;
    id: number;
    title: string;
    completed: boolean;
};

export const load: PageServerLoad<{
    todos: Promise<Todo[]>;
}> = async ({ fetch }) => {
    // fetch sample data
    return {
        todos: new Promise<Todo[]>((resolve) => {
            setTimeout(() => {
                fetch('https://jsonplaceholder.typicode.com/todos')
                    .then((response) => response.json())
                    .then((todos) => {
                        resolve(todos);
                    });
            }, 2000);
        }),
    };
};
```

```svelte:+page.svelte
<script lang="ts">
    import type { PageServerData } from './$types';

    export let data: PageServerData;
</script>

<main>
    <div>
        {#await data.todos}
            loading...
        {:then todos}
            <ul>
                {#each todos as todo}
                    <li>{todo.title}</li>
                {/each}
            </ul>
        {/await}
    </div>
</main>
```

このrouteは下記の順序で処理されるよう、記述されています。

1. loading...が表示される
2. サーバー側は、2秒待機->fetch()->json()を実行した結果を返す`Promise`を`load()`で返す
4. コンポーネント側は、#awaitブロックにより、Promiseが解決したタイミングで表示を切り替える（＝2秒程度待ったらTODOリストが表示される）


![Screenshot 2024-09-21 at 11.48.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/70db02bb-cb34-6908-06c7-606e81b82f64.png)
*Promiseの解決前*

![Screenshot 2024-09-21 at 11.48.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/c95a3f0c-dcd5-c3dc-2ec1-e04b78a9b4ab.png)
*解決後*

この実装で、開発サーバーではストリーミングが動作しますが、結果としてはAmplify環境では動作しませんでした。エラーが出るとかではなく、ストリーミングしたい要素まで全てが読み込まれた状態で初めてページがレスポンスされる挙動となります（なのでSSR自体は動作している）。

Amplifyにストリーミングレスポンスを有効化するようなオプションはなく、下記の記事でも言及されているように、現状でストリーミングレスポンスは非対応のようです。SSR Streamingはパフォーマンスの観点でたいへん有用ですので対応が待たれます。

https://zenn.dev/devcamp/articles/4a4e559d8544bc#3.-ssr-streaming-%E3%81%AE%E6%A9%9F%E8%83%BD

## 使用感

### Cloudflare Pagesとの比較

- 同じくらいの手軽さでSSRできるSvelteKitをデプロイできる
- Pagesはアーティファクトサイズ制限がかなり厳しい・ランタイムが薄く利用出来ないAPIがあるので、その点でかなり優位
- AmplifyはもちろんエッジSSRではないことは割り引いて見る必要がある
- ビルドは遅い（Amplifyが）

### AWS Lambdaとの比較

- LambdaでSvelteKitをデプロイするには、同じくサードパーティのアダプターが必要（[jill64/sveltekit-adapter-aws](https://github.com/jill64/sveltekit-adapter-aws)）
  - - 静的アセットはS3から・それ以外はLambdaから返すなど、いくつかのアーキテクチャパターンを選択できる
  - 自動でCDKをジェネレートするような作り
- Amplifyが利用出来るならこの手段を取る必要はないかもしれない
- ただしLambdaはストリーミングレスポンスをサポートしている

### AWS Lambda(+ Lambda Web Adapter)との比較

- ローカルでもコンテナ開発が前提なら、Lambda Web Adapter（LWA）利用のパターンの方がポータブルなコードを書けるので、Amplifyよりも便利かもしれない
- そこそこ厚いランタイムの上でSSRしたいけど、そのためだけにコンテナを利用するのはどうみても過剰、みたいなケースではLWAよりAmplifyを利用した方が幸せになれそう
- LWAでは静的ファイルまでもがLambdaを通じて配信される単には留意

## まとめ

- SvelteKitもAmplifyでお手軽にデプロイできる
- SSRも当然に動作するがストリーミングは未対応
- AmplifyはAWSサービスとの統合にも強みがあるはず（別途検証）