はじめに
個人開発で「Next.js × Supabase × Vercel」の構成を使い、筋トレ記録アプリを作っています。
Supabaseは「PostgreSQLベースのBaaS」として非常に優秀で、Auth・Storage・RLSまで一通り揃っています。
正直、Firebaseよりも「ちゃんとDBしている」感があり、個人開発との相性はかなり良いです。
しかし、実際に使ってみると分かります。
“なんとなく”では絶対に進めない。
特に以下のようなポイントで何度もハマりました。
- user_idがnullで怒られる
- RLSでinsertできない
-SQL関数で謎エラー
-本番環境でだけ動かない
この記事では、実際に遭遇したエラーとその原因、解決方法をまとめます。
1. null value in column "user_id" violates not-null constraint
エラー内容
ERROR: 23502: null value in column "user_id" violates not-null constraint
何が起きていたか
テーブル定義で user_id に NOT NULL 制約を付けていました。
user_id uuid references auth.users(id) not null
しかし、insert時に user_id を渡していなかったため、当然エラーになります。
原因
Supabase Authを使っているからといって、
user_idが自動でレコードに入るわけではない という点を理解していませんでした。
解決方法①:クライアント側で明示的に渡す
const {
data: { user }
} = await supabase.auth.getUser()
await supabase.from("user_plan_progress").insert({
user_id: user?.id,
weight: 60,
completed_sessions: 0
})
解決方法②:DB側でdefaultを設定する(おすすめ)
user_id uuid references auth.users(id) default auth.uid()
この設定をすると、insert時に明示しなくても auth.uid() が自動で入ります。
個人的にはDB側で担保する方が安全 だと感じました。
2. RLSでinsertできない(エラーが出ないのに保存されない)
これが一番ハマりました。
症状
insert処理は成功しているように見える
エラーは出ない
でもデータが入らない
原因
RLS(Row Level Security)が有効で、ポリシーを書いていなかった。
SupabaseではRLSが有効なテーブルに対して、
明示的なpolicyがないと操作できません。
解決方法
CREATE POLICY "Users can insert their own data"
ON user_plan_progress
FOR INSERT
WITH CHECK (auth.uid() = user_id);
さらに、SELECTも必要です。
CREATE POLICY "Users can read their own data"
ON user_plan_progress
FOR SELECT
USING (auth.uid() = user_id);
RLSを有効にしている場合は、
INSERT
SELECT
UPDATE
DELETE
それぞれにポリシーが必要です。
ここを理解していないと、永遠に「なぜか動かない」状態になります。
3. multiple assignments to same column エラー
エラー内容
ERROR: 42601: multiple assignments to same column "completed_sessions"
原因
SQL関数内で同じカラムを二重代入していました。
UPDATE user_plan_progress
SET completed_sessions = completed_sessions + 1,
completed_sessions = 0
WHERE id = target_id;
当然ですが、同じカラムを2回代入しているのでエラーになります。
学び
SupabaseのSQL Editorは便利ですが、
関数ロジックのチェックは自分でやるしかない。
アプリ側だけでなく、SQLもちゃんとレビューする必要があります。
4. SERVICE_ROLE_KEYとANON_KEYの違いを理解していなかった
Supabaseには2種類のキーがあります。
NEXT_PUBLIC_SUPABASE_ANON_KEY
クライアント側で使用
RLSが適用される
ブラウザ公開OK
SUPABASE_SERVICE_ROLE_KEY
サーバー専用
RLSを無視できる
絶対にクライアントに出してはいけない
Next.jsでAPI Routeを使う場合、サーバー側ではSERVICE_ROLE_KEYを使用できます。
例えば:
import { createClient } from "@supabase/supabase-js"
const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
管理系処理やバッチ処理は、こちらで実装するのが安全です。
5. Vercelデプロイ後に動かない問題
ローカルでは動くのに、本番では動かない。
原因はほぼこれです。
環境変数をVercelに登録していない。
Vercel → Project → Settings → Environment Variables
以下を必ず登録します。
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY
ローカルの .env.local は本番には反映されません。
Supabaseを使って感じたこと
Supabaseは確かに便利です。
しかし、
RLSを理解していない
認証とDB設計が曖昧
キーの責務を理解していない
この状態だと確実にハマります。
逆に言えば、
DB設計力とセキュリティ意識が一気に鍛えられる環境 でもあります。
個人開発でSupabaseを使うのは本当におすすめです。
ただし、「なんとなく使う」のではなく、
PostgreSQLの思想を理解しながら使うと、学習効率が一気に上がります。
まとめ
Supabaseで個人開発するときにハマりやすいポイントは:
user_idの扱い
RLSの理解不足
SQL関数のロジックミス
環境変数の設定漏れ
もし同じように詰まっている方がいれば、
まずはRLSとuser_idの設計を疑ってみてください。
Supabaseは「楽」ですが、「浅く使う」ことはできません。
その分、ちゃんと使えば確実に力になります。