Astro と Supabase を使ってブログサイトを作る中で、「クライアントとサーバーの役割の違い」を明確に理解することが大切だと感じました。
この記事では、実際に構築した例をもとにその考え方を整理します。
なぜ分けるのか
Supabaseには「読み取り専用」と「書き込み権限付き」の2種類のキーがあります。
クライアント(ブラウザ)は誰でもアクセス可能なため、
誤ってService Role Keyを埋め込むと、誰でもデータを削除できてしまうリスクがあります。
そのため、次のように分けて使います。
| 処理 | 実行環境 | 使用キー |
|---|---|---|
| 投稿・削除 | サーバー | SERVICE_ROLE_KEY |
| データ取得 | クライアント | ANON_KEY |
どこでコードが「実行されるか」
| 種類 | 実行場所 | 代表的なファイル | 主な役割 | 環境変数 |
|---|---|---|---|---|
| クライアント | ブラウザ(ユーザー側) |
supabaseClient.ts, index.astro
|
データの取得・表示 | import.meta.env.PUBLIC_〜 |
| サーバー | Astroのバックエンド(Node.js) |
supabaseServer.ts, /api/new.ts
|
データの追加・削除・保護された処理 | process.env.〜 |
クライアント側は読み取り専用
フロントエンドからは公開しても問題ないキーだけを使います。
例として、Supabaseのanon keyを使用します。
// src/lib/supabaseClient.ts
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL!;
const supabaseKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseKey);
- 記事の一覧を取得する
- 投稿を読み込む
といった処理に使用します。
サーバー側は安全な操作用
サーバー側ではService Role Key (書き込み権限付き)を使い、
Astro の APIルートなど非公開環境でのみ実行します。
// src/lib/supabaseServer.ts
import "dotenv/config";
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.SUPABASE_URL!;
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
export const supabaseServer = createClient(supabaseUrl, supabaseKey);
主に
- 記事投稿API
- データの更新・削除
などで利用します
まとめ
| 内容 | ファイル | 環境変数 | 注意点 |
|---|---|---|---|
| 記事投稿 |
/api/new.ts + supabaseServer.ts
|
SUPABASE_SERVICE_ROLE_KEY |
サーバーのみで使用 |
| 記事一覧表示 |
/index.astro + supabaseClient.ts
|
PUBLIC_SUPABASE_ANON_KEY |
公開OK |
| 認証など | 両方 | 各種 | 実行環境を意識する |
Supabaseを扱う際は、
「どこで実行されるコードなのか」を意識するだけでエラーやセキュリティ問題を防げます。
Astroのようにフロントとサーバーの両方を扱うフレームワークでは、
環境変数のプレフィックス(PUBLIC_)を分けることが特に重要でした。