Next.jsとSupabaseの連携エラーの対処

ご無沙汰しております。
ただいま[Next.jsを公式チュートリアル](https://nextjs.org/learn)で学習しているのですが、Chapter7の「Fetching Data」で大きく詰まってしまったため、その時の対処を記事として起こしました。

# 発生したエラー①
チュートリアルでは「Neon」でのセットアップを取り扱っていたのですが、Neonは別の用途で用いる予定がありましたので、「Supabase」の方を使用することにしました。
Supabase自体のセットアップは滞りなくでき、その後もチュートリアルに沿って.envファイルの設定とコーディングは進められ、テストデータの挿入も問題なくできたのですが、localhostで動作確認を行ったところ、下図のエラーが発生しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/4f870414-44c7-4798-89bb-aff8ce3a4788.png)
page.tsxの中身を確認しましたが、タイポなどが見当たらなかったのでログを確認すると、404のエラーが出ておりました。
```ts:エラーログ
Database Error: NeonDbError: Server error (HTTP status 404): Not Found
    at execute (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:1554:29)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async fetchRevenue (webpack-internal:///(rsc)/./app/lib/data.ts:22:22)
    at async Page (webpack-internal:///(rsc)/./app/dashboard/page.tsx:17:21) {
  code: null,
  sourceError: undefined
}
 ⨯ app\lib\data.ts (27:11) @ fetchRevenue
 ⨯ Error: Failed to fetch revenue data.
    at fetchRevenue (./app/lib/data.ts:27:15)
    at async Page (./app/dashboard/page.tsx:17:21)
digest: "507392606"
  25 |   } catch (error) {
  26 |     console.error('Database Error:', error);
> 27 |     throw new Error('Failed to fetch revenue data.');
     |           ^
  28 |   }
  29 | }
  30 |
 GET /dashboard 500 in 5939ms


import { createClient } from '@/utils/supabase/server';

export default async function Instruments() {
  const supabase = await createClient();
  const { data: instruments } = await supabase.from("instruments").select();

  return <pre>{JSON.stringify(instruments, null, 2)}</pre>
}

import { createServerClient } from '@supabase/ssr'
  import { cookies } from 'next/headers'

  export async function createClient() {
    const cookieStore = await cookies()

    return createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          getAll() {
            return cookieStore.getAll()
          },
          setAll(cookiesToSet) {
            try {
              cookiesToSet.forEach(({ name, value, options }) =>
                cookieStore.set(name, value, options)
              )
            } catch {
              // The `setAll` method was called from a Server Component.
              // This can be ignored if you have middleware refreshing
              // user sessions.
            }
          },
        },
      }
    )
  }
```
# 原因と対処①
上記のログを元に調べたところ、どうやらNext.jsにおけるDB接続はデフォルトだとNeonでの接続を想定したつくりになっているらしく、Supabaseでの接続は確立されていないみたいでした。
チュートリアルでNeonを例に挙げていたのは、最も設定の手間がかからないからだと思います。
data.tsのDBとの接続部分を以下に書き換えたところ、挿入したテストデータが画面に表示されました。
```diff_typescript:data.ts
- import { sql } from '@vercel/postgres';
+ import { db } from '@vercel/postgres';
import {
  CustomerField,
  CustomersTableType,
  InvoiceForm,
  InvoicesTable,
  LatestInvoiceRaw,
  Revenue,
} from './definitions';
import { formatCurrency } from './utils';

export async function fetchRevenue() {
  try {
    // Artificially delay a response for demo purposes.
    // Don't do this in production :)

    console.log('Fetching revenue data...');
    await new Promise((resolve) => setTimeout(resolve, 3000));
-   const data = await sql<Revenue>`SELECT * FROM revenue`;
 
+   const client = await db.connect();
+   const data = await client.sql<Revenue>`SELECT * FROM revenue`;

    console.log('Data fetch completed after 3 seconds.');

    return data.rows;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch revenue data.');
  }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/bbd02607-6baf-4345-9555-de9301a0235d.png)

# 発生したエラー②
localhostで画面表示ができたのでリポジトリにプッシュしVercelへデプロイしたところ、下図のエラーが発生しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/508bb794-b357-48fb-afbd-be15cac4ac54.png)

ビルドエラーとのことなのでログを確認したところ、「nodeのモジュールで足りないものがある」ことと「接続が確立していない」だと読み取りました。
```ts:モジュール不足
[01:31:52.212] > Build error occurred
[01:31:52.214] Error: Failed to collect page data for /seed
[01:31:52.214]     at /vercel/path0/node_modules/.pnpm/next@15.0.0-canary.56_react-dom@19.0.0-rc-f38c22b244-20240704_react@19.0.0-rc-f38c22b24_4ff65fda31da0994155383043ac8ca67/node_modules/next/dist/build/utils.js:1272:15
[01:31:52.215]     at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
[01:31:52.215]   type: 'Error'
[01:31:52.215] }
[01:31:52.247]  ELIFECYCLE  Command failed with exit code 1.
[01:31:52.265] Error: Command "pnpm run build" exited with 1
[01:31:52.587] 
```
```ts:接続エラー
Error: Connection terminated unexpectedly
    at un.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:1258:44)
    at Object.onceWrapper (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:422:39)
    at un.emit (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:409:63)
    at x.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:1210:66)
    at x.emit (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:409:63)
    at WebSocket.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:998:51)
    at callListener (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/event-target.js:290:14)
    at WebSocket.onClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/event-target.js:220:9)
    at WebSocket.emit (node:events:518:28)
    at WebSocket.emitClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/websocket.js:260:10)
    at TLSSocket.socketOnClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/websocket.js:1272:15)
    at TLSSocket.emit (node:events:530:35)
    at node:net:337:12
    at TCP.done (node:_tls_wrap:657:7)
    at TCP.callbackTrampoline (node:internal/async_hooks:130:17)
 ⨯ node_modules\.pnpm\@neondatabase+serverless@0.7.2\node_modules\@neondatabase\serverless\index.mjs (1244:1) @ un.eval
 ⨯ uncaughtException: Error: Connection terminated unexpectedly
    at un.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:1258:44)
    at Object.onceWrapper (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:422:39)
    at un.emit (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:409:63)
    at x.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:1210:66)
    at x.emit (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:409:63)
    at WebSocket.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:998:51)
    at callListener (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/event-target.js:290:14)
    at WebSocket.onClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/event-target.js:220:9)
    at WebSocket.emit (node:events:518:28)
    at WebSocket.emitClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/websocket.js:260:10)
    at TLSSocket.socketOnClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/websocket.js:1272:15)
    at TLSSocket.emit (node:events:530:35)
    at node:net:337:12
    at TCP.done (node:_tls_wrap:657:7)
    at TCP.callbackTrampoline (node:internal/async_hooks:130:17)
  1242 | t.ssl?n.requestSsl():n.startup(t.getStartupConf())}),n.on("sslconnect",function(){
  1243 | n.startup(t.getStartupConf())}),this._attachListeners(n),n.once("end",()=>{let i=this.
> 1244 | _ending?new Error("Connection terminated"):new Error("Connection terminated unex\
       | ^
  1245 | pectedly");clearTimeout(this.connectionTimeoutHandle),this._errorAllQueries(i),this.
  1246 | _ending||(this._connecting&&!this._connectionError?this._connectionCallback?this.
  1247 | _connectionCallback(i):this._handleErrorEvent(i):this._connectionError||this._handleErrorEvent(
 ⨯ node_modules\.pnpm\@neondatabase+serverless@0.7.2\node_modules\@neondatabase\serverless\index.mjs (1244:1) @ un.eval
 ⨯ uncaughtException: Error: Connection terminated unexpectedly
    at un.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:1258:44)
    at Object.onceWrapper (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:422:39)
    at un.emit (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:409:63)
    at x.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:1210:66)
    at x.emit (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:409:63)
    at WebSocket.eval (webpack-internal:///(rsc)/./node_modules/.pnpm/@neondatabase+serverless@0.7.2/node_modules/@neondatabase/serverless/index.mjs:998:51)
    at callListener (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/event-target.js:290:14)
    at WebSocket.onClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/event-target.js:220:9)
    at WebSocket.emit (node:events:518:28)
    at WebSocket.emitClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/websocket.js:260:10)
    at TLSSocket.socketOnClose (webpack-internal:///(rsc)/./node_modules/.pnpm/ws@8.14.2_bufferutil@4.0.8_utf-8-validate@6.0.3/node_modules/ws/lib/websocket.js:1272:15)
    at TLSSocket.emit (node:events:530:35)
    at node:net:337:12
    at TCP.done (node:_tls_wrap:657:7)
    at TCP.callbackTrampoline (node:internal/async_hooks:130:17)
  1242 | t.ssl?n.requestSsl():n.startup(t.getStartupConf())}),n.on("sslconnect",function(){
  1243 | n.startup(t.getStartupConf())}),this._attachListeners(n),n.once("end",()=>{let i=this.
> 1244 | _ending?new Error("Connection terminated"):new Error("Connection terminated unex\
       | ^
  1245 | pectedly");clearTimeout(this.connectionTimeoutHandle),this._errorAllQueries(i),this.
  1246 | _ending||(this._connecting&&!this._connectionError?this._connectionCallback?this.
  1247 | _connectionCallback(i):this._handleErrorEvent(i):this._connectionError||this._handleErrorEvent(
```
# 原因と対処②
接続エラーなので.envファイルにセットした値とSupabaseの値が合致しているか確認し調査したところ、Supabase側でも環境変数を設定しなければならないことが分かりました。
そこで、以下の方法でSupabase側に環境変数を設定いたしました。

### 設定値の確認
Supabaseのプロジェクトダッシュボードから Project Settings > Data API を開く。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/3f9f14e1-e8e1-4ab8-85ea-bb44209434f5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/b61629b0-da2f-44ed-a0fa-bef2d4d3ac3f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/57c13783-ba34-4337-98d5-b2ea8da05473.png)

Data APIの中の「Project URL」の「URL」と「Project API Keys」の「`anon` `pubilic`」の値をそれぞれメモ帳などに控えます。
又、この設定値は[Chapter6](https://nextjs.org/learn/dashboard-app/setting-up-your-database#:~:text=Once%20connected%2C%20navigate%20to%20the%20.env.local%20tab%2C%20click%20Show%20secret%20and%20Copy%20Snippet.%20Make%20sure%20you%20reveal%20the%20secrets%20before%20copying%20them.)にて.envファイルに設定したものと同じ(NEXT_PUBLIC_SUPABASE_URLとNEXT_PUBLIC_SUPABASE_API_KEY)であるため、こちらも合わせて確認しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/7fb9bd6a-e32c-4f37-a687-96d8af60da42.png)

### Environment Variablesへの設定
Vercelのプロジェクトダッシュボードから Settings > Environment Variables を押下する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/34538695-cca5-4a15-acfa-261069bb2be0.png)

「Create new」欄に名前と値をそれぞれセットします。
「NAME」には.envファイルで設定した名称を、「Value」には上述で控えた値をそれぞれセットします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/6ce843b8-1091-49c1-a682-e62629973c39.png)

セットしましたら右下の「Save」ボタンを押下し、「Redeploy」を押下して再度デプロイし直します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/b28876d6-8ab1-428d-826e-b5d293086424.png)

再デプロイが成功し、エラーが解消されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/9e34822b-d70e-4372-b385-7278f440b937.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506986/8eb43796-74b8-43e8-b2c7-c5990273504a.png)

# まとめ
非常に長時間かけて苦戦してしまいましたが、何とかNext.jsとSupabaseの連携ができました。
「**チュートリアルで扱っているものは例題でも素直に従う**」
「**チュートリアルで出していることはちゃんと理由がある**」
基本中の基本を怠ってしまったことの戒めとして、筆を執らせていただきました。
しかし、「Supabaseというものがある」「Next.jsではデフォルトだとNeonというサービスと連携できるようになっている」ことを学びました。
引き続きチュートリアルを進め、理解を深めて参ります。

# 参考文献
[Next.js のビルド時に Error: Cannot find module '/vercel/path0/node_modules/.pnpm/bcrypt エラーが出たときの対処法](https://qiita.com/nskydiving/items/153e2ab5068feebbead3)
[Next.jsチュートリアル](https://zenn.dev/xylocopa/scraps/41ea3bf8a0f0a8)
[Next.js ✖️ SupabaseプロジェクトのVercelへのデプロイメモ](https://zenn.dev/shgs/articles/6b7fde20a6a596#vercel%E3%81%A7%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E2%91%A1)