Zenn
🍆

【Supabaseローカル重すぎ!】同じWi-Fiに繋がった別マシンで認証処理やDBを使いたい

2025/03/06に公開

Supabase CLIで supabase start を実行するだけで、ローカル環境で Supabaseの認証機能・DBが簡単に使えるようになります。とても手軽ではあるのですが、欠点としては「重すぎる」という問題があります。Dockerコンテナが10個ほど立ち上がってしまうので仕方がありませんね。

とはいえ、重いままでは作業がしにくいので少しでも軽くする方法を考えます。

そこで考えたのが、「Next.jsでの開発」と「Supabaseの認証処理とDB」の物理的なマシンを分けるという方法です。本記事では、プライベートネットワーク内で物理的にアプリケーションサーバー(フロントエンド)とバックエンドサーバーを分ける方法をご紹介します!

実現したいこと

物理的にマシンを分けていますが、今までのSupabaseでのローカル開発と遜色ない状態にします。開発マシンから localhost:54323 にアクセスすればSupabase Studio(ダッシュボード)が開き、 localhost:54321 を叩けば Supabase APIサーバーに繋がるという環境を目指します。

一見、普通に開発マシン内で動いているように見えるが、実は裏側でSupabaseマシンにリクエストが飛んでいるという状況をつくるという訳ですね。別マシンで動いてることすら意識しない環境にします!

1. まずは基礎知識を抑える

何も知らない方に向けて事前知識を。

一見、開発マシンからSupabaseマシンの環境にアクセスするのは難しそうに見えますが、同じWi-Fiに接続されていれば全く難しいことはありません。ちょっと試してみましょう。

まずはOSに応じた方法でSupabaseマシンのIPアドレスをメモします。

Windowsの場合

コマンドプロンプトで ipconfig コマンドを叩いてみてください。
「Wireless LAN adapter Wi-Fi」という欄があり、そこの「IPv4 アドレス」という箇所がIPアドレスです。

Macの場合

Appleマーク → システム設定 → Wi-Fi → (接続中のWi-Fi欄の)詳細ボタン → TCP/IP → 「IPアドレス」の欄

僕の環境ではSupabaseマシンが Mac なので上記のようになっていました。( 192.168.0.13

この例では、開発マシンのブラウザから http://192.168.0.13:54323 にアクセスすると、SupabaseのGUIが開けます。これは開発マシンからSupabaseマシンの localhost:54323 にアクセスできている状態です。

このように同じ Wi-Fi内 であれば、IPアドレス:ポート番号 という形式でアドレスを打ち込めば、ローカルサーバーにアクセスできるのです。

2. リバースプロキシを用意しよう!

そのまま Next.js に 192.168.0.13:54321 と置き換えてしまっても良いのですが結構問題が出てきます。その代表的な例として、メール認証のURLやプロバイダー認証(GoogleやXなどのOAuthのこと)で正常に処理できない問題があります。特にOAuthではコールバックURLを localhost 以外受け付けてくれないので、認証機能がローカルで使えないという問題が発生してしまいます。

それを解決してくれるのが「リバースプロキシ」です。

リバースプロキシとは、リクエストとレスポンスを仲介してくれるサーバーです。例えば、 localhost:54321 でリクエスト投げたものを、192.168.9.13:54321 にリクエスト投げ直してくれてその結果をそのまま localhost:54321 として返してくれるものです。

この開発環境のためだけにソースを改変する必要がありませんから、そういった意味でもリバースプロキシは有用な手段だと言えます。

2-1. リバースプロキシを実装しよう

リバースプロキシの実装は簡単です。
新しいプロジェクトフォルダを作成し、下記に折りたたんであるファイルとソースをコピペするだけでOKです。(全部Geminiさんに作ってもらったんですけどね......。)

なお、 serversディレクトリ 内の2つのファイルは【ここにSupabaseマシンのIPアドレス】をご自身のものに置き換えてください。

package.json
package.json
{
  "dependencies": {
    "@types/express": "^5.0.0",
    "@types/node": "^22.13.9",
    "child_process": "^1.0.2",
    "express": "^4.21.2",
    "http-proxy-middleware": "^3.0.3",
    "typescript": "^5.8.2"
  },
  "devDependencies": {
    "ts-node": "^10.9.2"
  }
}
tsconfig.json
tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}
serversディレクトリ
servers/api.ts
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";

const app = express();
const port = 54321;
const target = "http://【ここにSupabaseマシンのIPアドレス】:54321";

app.use(
  "/",
  createProxyMiddleware({
    target,
    changeOrigin: true,
  })
);

app.listen(port, () => {
  console.log(`Proxy server listening at http://localhost:${port}`);
});
servers/dashboard.ts
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";

const app = express();
const port = 54323;
const target = "http://【ここにSupabaseマシンのIPアドレス】:54323";

app.use(
  "/",
  createProxyMiddleware({
    target,
    changeOrigin: true,
  })
);

app.listen(port, () => {
  console.log(`Proxy server listening at http://localhost:${port}`);
});
server.ts
import { spawn } from "child_process";
import * as path from "path"; // path モジュールをインポート

function runScript(scriptPath: string, scriptName: string) {
  const child = spawn("npx", ["ts-node", scriptPath], { stdio: "inherit" });

  child.on("error", (err) => {
    console.error(`${scriptName} failed to start:`, err);
  });

  child.on("exit", (code) => {
    if (code !== 0) {
      console.error(`${scriptName} exited with code ${code}`);
    } else {
      console.log(`${scriptName} exited successfully.`);
    }
  });

  return child;
}

// 相対パスを修正
const apiPath = path.join(__dirname, "servers", "api.ts");
const dashboardPath = path.join(__dirname, "servers", "dashboard.ts");

const apiProcess = runScript(apiPath, "API Server");
const dashboardProcess = runScript(dashboardPath, "Dashboard Server");

process.on("SIGINT", () => {
  console.log("Terminating processes...");
  apiProcess.kill("SIGINT");
  dashboardProcess.kill("SIGINT");
});

child_process によって並列実行しています。

ディレクトリツリー

完成したら下記の構成になっているはずです。

├── 📄tsconfig.json
├── 📄package.json
├── 📄server.ts
└── 📁servers/
    ├── 📄api.ts
    └── 📄dashboard.ts

2-2. リバースプロキシを起動して確認してみよう!

ここまでできればリバースプロキシを起動して確認するだけです。

npx ts-node server.ts

でリバースプロキシを起動しましょう。
起動できたらターミナル内に Proxy server listening at ~ という文言が出てきます。

この状態で localhost:54323 にブラウザで開発マシンからアクセスしてみてください。
開発マシンにはDockerが立ち上がってないはずが、しっかりと Supabase Studio(GUI)が動いているのが確認できることでしょう。

これで物理的なマシンを分割することができました!

Next.js側では特にソースコードの改変は不要です。あとは今まで通りの開発ができるはずです!

おしまい

開発マシンが Docker から解放されてかなり軽くなりました!
「Supabase開発は重すぎて進まない」とは無縁の環境を手に入れることができました!!

Discussion

ログインするとコメントできます