Nuxt3+Supabaseに移行してCloudflare Pagesでホスティング

## 経緯
このような環境で開発していたアプリがあります。

https://qiita.com/JQinglong/items/e35294f7a260c5a26ae8#django-rest-api-%E3%81%AE-heroku%E3%81%B8%E3%81%AE%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9

これに対して、
- 似たようなターゲットで似たような名前のアプリがあるので、名前を変えたい
- 色々な環境を最新化したい
- 自前で構築している部分をサービス利用方式にしたい

など考えて、移行することにしました。

## 選定
### フロントフレームワーク
これは、あまり変えるつもりはなく、素直にNuxtを3に上げることにします。
Composition APIは使っていたので、流用度は大きいのではないかと期待もありますが、そもそもバックエンドを変えたら全く変わるのではないかとも思っています（未着手）。

### バックエンド
自前のVPSにMySQL立てているのがそもそもいやだなという気持ち。
自由にいじれそうという意味では、Dokkuというのも気になりましたが、その意味であまり検討しませんでした。
候補としては、Render.comも気になりました。全部入り的な感じで、今も気になっています。
が、今回はSupabase。
素のPostgreSQLが使えそうというのが魅力的で、
認証もできるし、いざという時にEdgeFunctionというのも助けになりそう。

### 画面デザイン
Nuxt+Vuetifyもそこまで不満ではなかったですが、若干Nuxt3対応で不安を感じさせる情報も目にしたりして、変えてみることにしました。
一度使ってみたかったTailwindを中心に見ていくと、Tailwind+daisyUIがよく使われていそう。
必要なコンポーネントもありそうなので（というか、気にしたのは、NavBarとMenuくらいかもですが）、これにします。

### ホスティング先
元々使っていたNetlifyは何の不満もありませんでしたが、改めて調査。
この観点でもRender.comが気になりましたが、
- 複数管理者を設定できる
- 日本語対応

というのに魅力を感じて、Cloudflare Pagesにしました。

## 構築

本投稿では、導入の最初の最初の部分までですが、思ったよりは快適に進んだ気がします。
詳細は下記から。

https://github.com/JQinglong/gijilink/commits/main/

### Docker
これまでは、db, api, web　のコンテナ３点セットを立ち上げていたのですが、Supabaseは@nuxtjs/supabase モジュールで直接アクセスできそうということで、web用コンテナのみ。

```yaml:docker-compose.yml
version: '3.9'

services:
  web:
    container_name: gijilink-web
    build: docker/web
    volumes:
      - ./gijilink-web:/app:cached
      - nuxt_node_modules:/app/node_modules
    ports:
      - "3000:3000"
      - "24678:24678"
    tty: true
    environment:
      - HOST=0.0.0.0
      - port=3000
      - CHOKIDAR_USEPOLLING=true
    command: sh -c "yarn install && yarn dev"
volumes:
  nuxt_node_modules:
```

```dockerfile: docker/web/Dockerfile
FROM node:20-slim

ENV TZ Asia/Tokyo

WORKDIR /app

RUN apt-get update \
    && apt-get install -y \
    git \
    vim
```

gijilink-webのところは適宜変更してください。

また、最初は

```yml
command: sh -c "yarn install && yarn dev"
```
はコメントアウトして起動して、下記のNuxtインストールができたら、コメントを外す感じです。

### Supabaseモジュール

Nuxtモジュール側のドキュメント
https://supabase.nuxtjs.org/get-started

Supabase側のドキュメント
https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3

両方睨みながら、基本的には、Supabase側のドキュメントに従って進めます。
事前に、Supabase登録をしておいて、テーブル作って、APIキーを確認しておくところまではやっておきます。

Nuxtアプリも作成します。

```sh
docker-compose up -d
docker exec -it gijilink-web bash
/app# npx nuxi init . --force
```

Nuxtインストールも色々な指定がなくなっているのですね。
package managerはyarnを選択しました。
git repositoryはNoにしました。

ここで、上記コメントを外して、起動確認ができると思います。-d なしで、

```
docker-compose up
```

が分かりやすいかと思います。

そして、この環境にモジュールを導入します。
@supabase/supabase-js を入れている情報も見かけましたが、@nuxtjs/supabase  でそれも含めて入れてくれます。

```sh
/app# yarn add @nuxtjs/supabase
```

```
info Direct dependencies
└─ @nuxtjs/supabase@1.1.6
info All dependencies
├─ @nuxtjs/supabase@1.1.6
├─ @supabase/functions-js@2.2.0
├─ @supabase/gotrue-js@2.62.2
├─ @supabase/postgrest-js@1.11.0
├─ @supabase/realtime-js@2.9.3
├─ @supabase/storage-js@2.5.5
├─ @supabase/supabase-js@2.39.3
├─ @types/phoenix@1.6.4
└─ @types/ws@8.5.10
```

以下、一旦は、Supabase側のドキュメントに従って、ソースコピペしながら、magic link で認証させる画面を作成します。
.envファイルを作っておくことで、
const supabase = useSupabaseClient();
で接続してくれるということですね。

ただ、nuxt.config.ts　はそのままでは動かなかったので、一部削除して下記のようにしています。

```ts: nuxt.config.ts
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
  modules: ['@nuxtjs/supabase'],
})
```

なお、今に至るまで、magic link　は動作していません。
```
POST https://xxx.supabase.co/auth/v1/token?grant_type=pkce 403 (Forbidden)
```
となっているのが問題だと思われますが、原因不明です。
一旦は、そのまま進めます。

### 画面デザイン
daisyUIを使うために何を入れれば良いかということで、Tailwind CSSを導入したいわけですが、これも道が色々ありそう。

Tailwind側のドキュメント
https://tailwindcss.com/docs/guides/nuxtjs

Nuxt TailwindCSS moduleのドキュメント
https://tailwindcss.nuxtjs.org/getting-started/installation

Nuxt UIのドキュメント
https://ui.nuxt.com/getting-started

これについては、Nuxt UIにTailwind CSSが含まれており、Nuxt UIとしてのコンポーネントが提供されている。どの程度重くなるか分かりませんが、大は小を兼ねるということで、まずはNuxt UIを入れます。

Nuxt UIのドキュメントのSetupの手順を実施します。
nuxt.config.ts　は下記のようになります。

```ts: nuxt.config.ts
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
  modules: ['@nuxtjs/supabase', '@nuxt/ui'],
})
```

適用後は、components/Auth.vue に、下記を入れておけばより分かりやすいかと思います。

```vue
  <h1 class="text-3xl font-bold underline">
    Hello world!
  </h1>
```

そうしたら、さらに、daisyUI。

https://daisyui.com/docs/install/

右上の設定で、日本語に切り替えられるのがちょっと助かります。
インストールは、

```sh
/app# yarn add daisyui
```

でOK。
ドキュメント上は、その後、tailwind.config.js への追加について記載されていますが、動作確認としてはテーマの適用までやったほうが良いかと思うので、下記にしておきます。

```ts: tailwind.config.js
export default {
  content: [
    "./components/**/*.{js,vue,ts}",
    "./layouts/**/*.vue",
    "./pages/**/*.vue",
    "./plugins/**/*.{js,ts}",
    "./nuxt.config.{js,ts}",
  ],
  theme: {
    extend: {},
  },
  plugins: [require("daisyui")],
}
```

そして、さまざまなタイプのボタンを貼ってみると、ガラリと見た目が変わるのがわかります。
（適用されない場合は、docker起動し直し）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77307/cfa0d913-eb43-f2ac-e5e1-1db5a966c0b0.png)


### デプロイ
Nuxtとしてもデプロイ先の情報をまとめてくれていて、その中にCloudflareもあります。
https://nuxt.com/deploy
が、本題的なことを書いてくれていないので、Cloudflareのドキュメントを参照します。

Cloudflareのドキュメント
https://developers.cloudflare.com/pages/framework-guides/deploy-a-nuxt-site/#git-integration

- Githubに登録
- CloudflareのPagesアプリケーションを作成してGithubのリポジトリに接続
ここで、最初メニューを開いてもPagesがないなと思っていたのですが、「概要」を開いて、その中の「Pages」タブから作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77307/9253ba8c-5d0b-31ad-a3a7-9814514d4707.png)

また、ビルドの構成の設定以外に、.envに記載している内容を環境変数として登録する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77307/626b850e-03d1-e5a4-8212-31397abf2c44.png)

### データ移行
既存アプリのデータはMySQLですが、これを移行しました。
なんと、Supabase用に、マイグレーションツールが用意されています。
https://supabase.com/docs/guides/resources/migrating-to-supabase/mysql

Google Colabで、変数設定して実行するだけで、簡単に移行できました。
変数の、SUPAVISOR_URLは、最初[YOUR-PASSWORD]の部分は自分で置換するのかと思ったらうまく行きませんでしたが、そこはそのまま貼り付けて、SUPABASE_PASSWORDの方に書いておけば、実行時に置換してくれるのでした。

また、MySQLのdatabase名がPostgreSQLのschemaとして入ってきます。名前を変えておきたいと思いましたが、
ALTER SCHEMA aaa RENAME TO bbb
で一発。普通にPostgreSQLと思えば良いというのは、ストレスもないです。

ただし、このままでは、API経由でアクセス可能になっていません。
初期状態では、publicスキーマのみが公開されています。
https://supabase.com/docs/guides/api/using-custom-schemas

追加schemaを公開するためには、記載されているSQLを実行する必要があるということですが、その上で利用する方法は、createClientする方法しか書かれていません。
useSupabaseClientで読み込ませるにはどうすればよいか。

createClientはSupabaseとしてのJavaScript Client Libraryの機能で、それとしては対応している。
https://supabase.com/docs/reference/javascript/initializing

しかし、それを呼び出す、NuxtとしてのSupabaseモジュールは、そこまでは対応していないということのようです。
https://github.com/nuxt-modules/supabase/blob/main/src/module.ts

もっとも、NuxtSupabaseのドキュメントでも、Realtimeの項では、schema指定に触れられておりますが、
https://supabase.nuxtjs.org/usage/composables/usesupabaseclient#realtime
これも、要は、RealtimeChannelがsupabase-jsの機能なので、指定方法が用意されている、ということのようです。

いざという時はそれもありですが、常にそちらに依存してしまうとNuxtSupabaseを使う意味も無くなってきてしまうと思うので、publicスキーマにテーブルを移動して良いのではと思いました。

PostgreSQLの中の世界ですが
```sql
select 'ALTER TABLE before_schema.' || tablename || ' SET SCHEMA public;' from pg_tables
where schemaname ='before_schema'
and tablename like 'abc%' --必要に応じて
```
という感じで、ALTER TABLE文を生成して、それを実行ですね。

publicのテーブルは普通に参照できるので、データアクセス用のcomponentを作ってみます。
（polilink_api_council というテーブルを参照しています）

```vue: components/Database.vue
<script setup>
const supabase = useSupabaseClient()
const councils = ref([]);

async function getCouncils() {
  const { data } = await supabase.from("polilink_api_council").select();
  councils.value = data;
}

onMounted(() => {
  getCouncils();
});
</script>

<template>
  <div>
    data
    <ul>
      <li v-for="council in councils" :key="council.id">
        {{ council.id }}
      </li>
    </ul>
  </div>
</template>
```
これをapp.vueに追加すれば、id一覧が出力できました。

## 今後
これで基本的な環境ができたということで、あとは現在Composition APIで作っている実実装をどのように移行していけるか、というところを試していきたいと思います。