supabase-troubleshooting
Supabase利用時に発生する認証エラー、RLSポリシー、接続問題、パフォーマンス低下といった一般的な課題を解決し、エラー修正や最適化を支援するSkill。
📜 元の英語説明(参考)
Troubleshoot common Supabase issues including auth errors, RLS policies, connection problems, and performance. Use when debugging Supabase issues, fixing errors, or optimizing performance.
🇯🇵 日本人クリエイター向け解説
Supabase利用時に発生する認証エラー、RLSポリシー、接続問題、パフォーマンス低下といった一般的な課題を解決し、エラー修正や最適化を支援するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o supabase-troubleshooting.zip https://jpskill.com/download/9506.zip && unzip -o supabase-troubleshooting.zip && rm supabase-troubleshooting.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9506.zip -OutFile "$d\supabase-troubleshooting.zip"; Expand-Archive "$d\supabase-troubleshooting.zip" -DestinationPath $d -Force; ri "$d\supabase-troubleshooting.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
supabase-troubleshooting.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
supabase-troubleshootingフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Supabase トラブルシューティングスキル
一般的な Supabase の問題をデバッグして修正します。
クイック診断
| 症状 | 考えられる原因 |
|---|---|
| 空のデータが返される | RLS ポリシーによるアクセス遮断 |
| "Not authorized" | 認証トークンの欠落または無効 |
| "JWT expired" | トークンの更新が必要 |
| クエリが遅い | インデックスの欠落または RLS サブクエリ |
| Connection refused | URL が間違っているか、サービスが停止している |
| "Duplicate key" | 一意性制約違反 |
| Function timeout | コールドスタートまたは重い計算 |
認証の問題
"Invalid login credentials"
// メールアドレスの形式を確認
const { data, error } = await supabase.auth.signInWithPassword({
email: email.toLowerCase().trim(), // メールアドレスを正規化
password
})
// ユーザーが存在するか確認
const { data } = await supabase
.from('auth.users') // 管理者のみ
.select('*')
.eq('email', email)
"Email not confirmed"
// オプション 1: 確認メールを再送信
const { error } = await supabase.auth.resend({
type: 'signup',
email: 'user@example.com'
})
// オプション 2: 管理者による確認 (サーバーサイド)
await supabaseAdmin.auth.admin.updateUserById(userId, {
email_confirm: true
})
"JWT expired"
// クライアントは自動的にリフレッシュしますが、強制的にリフレッシュすることもできます
const { data, error } = await supabase.auth.refreshSession()
// トークンのリフレッシュをリッスン
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'TOKEN_REFRESHED') {
// キャッシュされたトークンを更新
}
})
セッションが永続化されない
// ストレージの設定を確認
const supabase = createClient(url, key, {
auth: {
persistSession: true, // デフォルトは true
storage: localStorage // またはカスタムストレージ
}
})
// 複数のインスタンスがないか確認
// アプリごとに 1 つの Supabase クライアントのみを作成
Row Level Security (RLS) の問題
空のデータが返される
-- RLS が有効になっているか確認
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
-- 既存のポリシーを確認
SELECT * FROM pg_policies
WHERE tablename = 'your_table';
-- ユーザー ID でポリシーをテスト
SET request.jwt.claims = '{"sub": "your-user-uuid", "role": "authenticated"}';
SELECT * FROM your_table;
"violates row-level security policy"
// 認証されているか確認
const { data: { user } } = await supabase.auth.getUser()
console.log('Current user:', user?.id)
// RLS ポリシーの要件を確認
// よくある問題: auth.uid() が行の user_id と一致しない
RLS ポリシーのデバッグ
-- 詳細な RLS デバッグを有効にする (一時的)
SET log_statement = 'all';
SET log_min_duration_statement = 0;
-- 特定のユーザーとしてテスト
SET request.jwt.claims = '{
"sub": "user-uuid",
"role": "authenticated",
"aal": "aal1"
}';
-- クエリを実行
SELECT * FROM posts;
-- 認証関数を確認
SELECT auth.uid();
SELECT auth.role();
SELECT auth.jwt();
RLS のパフォーマンスの問題
-- 悪い例: 各行に対して auth.uid() を呼び出す
CREATE POLICY "slow" ON posts
USING (auth.uid() = user_id);
-- 良い例: auth.uid() の結果をキャッシュする
CREATE POLICY "fast" ON posts
USING ((SELECT auth.uid()) = user_id);
-- RLS 列のインデックスを追加
CREATE INDEX idx_posts_user_id ON posts(user_id);
データベースの問題
"duplicate key value violates unique constraint"
// insert の代わりに upsert を使用
const { data, error } = await supabase
.from('table')
.upsert({ id: existingId, ...values }, {
onConflict: 'id'
})
.select()
"foreign key violation"
// 参照先の行が存在することを確認
const { data: parent } = await supabase
.from('parent_table')
.select('id')
.eq('id', parentId)
.single()
if (!parent) {
// 最初に親を作成するか、エラーを処理
}
クエリが古いデータを返す
// 特定のクエリのキャッシュを無効にする
const { data } = await supabase
.from('table')
.select('*')
.eq('id', id)
.single()
.throwOnError() // 実際にはエラー時にスロー
// 最新のデータを強制的に取得
const { data } = await supabase
.from('table')
.select('*', { head: false, count: 'exact' })
接続の問題
"Failed to fetch" / ネットワークエラー
// Supabase URL を確認
console.log('URL:', process.env.NEXT_PUBLIC_SUPABASE_URL)
// API キーを確認
console.log('Key prefix:', process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY?.slice(0, 20))
// 接続をテスト
const { data, error } = await supabase.from('_health').select('*')
console.log('Health check:', { data, error })
ローカル開発環境での接続
# Docker が実行されているか確認
docker ps
# Supabase のステータスを確認
supabase status
# 必要に応じて再起動
supabase stop
supabase start
CORS の問題
// Edge function は CORS ヘッダーを含める必要がある
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
}
// 常に OPTIONS プリフライトを処理
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
ストレージの問題
アップロードの失敗
const { data, error } = await supabase.storage
.from('bucket')
.upload('path', file)
if (error) {
// エラーの種類を確認
if (error.message.includes('exceeded')) {
console.log('File too large')
} else if (error.message.includes('mime type')) {
console.log('Invalid file type')
} else if (error.message.includes('row-level security')) {
console.log('RLS policy blocking upload')
}
}
ストレージ RLS のデバッグ
-- ストレージポリシーを確認
SELECT * FROM pg_policies
WHERE tablename = 'objects'
AND schemaname = 'storage';
-- 一般的な修正: 認証されたユーザーが自分のフォルダーにアップロードできるようにする
CREATE POLICY "Upload to own folder"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'uploads'
AND auth.uid()::text = (storage.foldername(name))[1]
);
Edge Function の問題
Function Timeout (504)
// ブロッキング操作がないか確認
// 悪い例: 長い同期操作
const result = heavyComputation()
// 良い例: 操作を非同期に保つ
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Supabase Troubleshooting Skill
Debug and fix common Supabase issues.
Quick Diagnosis
| Symptom | Likely Cause |
|---|---|
| Empty data returned | RLS policies blocking access |
| "Not authorized" | Missing or invalid auth token |
| "JWT expired" | Token needs refresh |
| Slow queries | Missing indexes or RLS subqueries |
| Connection refused | Wrong URL or service down |
| "Duplicate key" | Unique constraint violation |
| Function timeout | Cold start or heavy computation |
Authentication Issues
"Invalid login credentials"
// Check email format
const { data, error } = await supabase.auth.signInWithPassword({
email: email.toLowerCase().trim(), // Normalize email
password
})
// Check if user exists
const { data } = await supabase
.from('auth.users') // Admin only
.select('*')
.eq('email', email)
"Email not confirmed"
// Option 1: Resend confirmation
const { error } = await supabase.auth.resend({
type: 'signup',
email: 'user@example.com'
})
// Option 2: Admin confirm (server-side)
await supabaseAdmin.auth.admin.updateUserById(userId, {
email_confirm: true
})
"JWT expired"
// Client auto-refreshes, but you can force it
const { data, error } = await supabase.auth.refreshSession()
// Listen for token refresh
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'TOKEN_REFRESHED') {
// Update any cached tokens
}
})
Session Not Persisting
// Check storage configuration
const supabase = createClient(url, key, {
auth: {
persistSession: true, // Default is true
storage: localStorage // Or custom storage
}
})
// Check for multiple instances
// Only create one Supabase client per app
Row Level Security (RLS) Issues
Empty Data Returned
-- Check if RLS is enabled
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
-- Check existing policies
SELECT * FROM pg_policies
WHERE tablename = 'your_table';
-- Test policy with your user ID
SET request.jwt.claims = '{"sub": "your-user-uuid", "role": "authenticated"}';
SELECT * FROM your_table;
"violates row-level security policy"
// Check you're authenticated
const { data: { user } } = await supabase.auth.getUser()
console.log('Current user:', user?.id)
// Check the RLS policy requirements
// Common issue: auth.uid() doesn't match row's user_id
Debug RLS Policies
-- Enable detailed RLS debugging (temporary)
SET log_statement = 'all';
SET log_min_duration_statement = 0;
-- Test as specific user
SET request.jwt.claims = '{
"sub": "user-uuid",
"role": "authenticated",
"aal": "aal1"
}';
-- Run your query
SELECT * FROM posts;
-- Check auth functions
SELECT auth.uid();
SELECT auth.role();
SELECT auth.jwt();
RLS Performance Issues
-- Bad: Calls auth.uid() for each row
CREATE POLICY "slow" ON posts
USING (auth.uid() = user_id);
-- Good: Caches auth.uid() result
CREATE POLICY "fast" ON posts
USING ((SELECT auth.uid()) = user_id);
-- Add indexes for RLS columns
CREATE INDEX idx_posts_user_id ON posts(user_id);
Database Issues
"duplicate key value violates unique constraint"
// Use upsert instead of insert
const { data, error } = await supabase
.from('table')
.upsert({ id: existingId, ...values }, {
onConflict: 'id'
})
.select()
"foreign key violation"
// Ensure referenced row exists
const { data: parent } = await supabase
.from('parent_table')
.select('id')
.eq('id', parentId)
.single()
if (!parent) {
// Create parent first or handle error
}
Query Returning Stale Data
// Disable cache for specific queries
const { data } = await supabase
.from('table')
.select('*')
.eq('id', id)
.single()
.throwOnError() // Actually throws on error
// Force fresh data
const { data } = await supabase
.from('table')
.select('*', { head: false, count: 'exact' })
Connection Issues
"Failed to fetch" / Network Error
// Check Supabase URL
console.log('URL:', process.env.NEXT_PUBLIC_SUPABASE_URL)
// Check API key
console.log('Key prefix:', process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY?.slice(0, 20))
// Test connection
const { data, error } = await supabase.from('_health').select('*')
console.log('Health check:', { data, error })
Local Development Connection
# Check Docker is running
docker ps
# Check Supabase status
supabase status
# Restart if needed
supabase stop
supabase start
CORS Issues
// Edge function must include CORS headers
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
}
// Always handle OPTIONS preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
Storage Issues
Upload Failing
const { data, error } = await supabase.storage
.from('bucket')
.upload('path', file)
if (error) {
// Check error type
if (error.message.includes('exceeded')) {
console.log('File too large')
} else if (error.message.includes('mime type')) {
console.log('Invalid file type')
} else if (error.message.includes('row-level security')) {
console.log('RLS policy blocking upload')
}
}
Storage RLS Debug
-- Check storage policies
SELECT * FROM pg_policies
WHERE tablename = 'objects'
AND schemaname = 'storage';
-- Common fix: Allow authenticated uploads to user folder
CREATE POLICY "Upload to own folder"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'uploads'
AND auth.uid()::text = (storage.foldername(name))[1]
);
Edge Function Issues
Function Timeout (504)
// Check for blocking operations
// Bad: Long synchronous operation
const result = heavyComputation()
// Good: Keep operations async and fast
const result = await heavyComputationAsync()
// Check limits:
// - CPU time: 2 seconds
// - Wall clock: 150s (400s on Pro)
Function Error (500)
// Always wrap in try-catch
try {
const result = await riskyOperation()
return new Response(JSON.stringify({ data: result }))
} catch (error) {
console.error('Function error:', error)
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500 }
)
}
Secrets Not Available
# Check secrets are set
supabase secrets list
# Set missing secrets
supabase secrets set API_KEY=value
# Note: Changes apply immediately, no redeploy needed
Realtime Issues
Not Receiving Events
// 1. Check table has realtime enabled
// SQL: ALTER PUBLICATION supabase_realtime ADD TABLE your_table;
// 2. Check subscription status
const channel = supabase
.channel('test')
.on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, callback)
.subscribe((status, err) => {
console.log('Subscription status:', status, err)
})
// 3. Check RLS allows SELECT
// Realtime respects RLS policies
"Tried to subscribe multiple times"
// Don't call subscribe() twice on same channel
const channel = supabase.channel('my-channel')
channel.subscribe() // OK
channel.subscribe() // Error!
// Cleanup before resubscribing
await supabase.removeChannel(channel)
const newChannel = supabase.channel('my-channel').subscribe()
Too Many Connections
// Clean up channels on unmount
useEffect(() => {
const channel = supabase.channel('data')
.on('postgres_changes', {...}, callback)
.subscribe()
return () => {
supabase.removeChannel(channel) // Important!
}
}, [])
Performance Optimization
Slow Queries
-- Use EXPLAIN ANALYZE to find bottlenecks
EXPLAIN ANALYZE SELECT * FROM posts WHERE user_id = 'uuid';
-- Add missing indexes
CREATE INDEX CONCURRENTLY idx_posts_user_id ON posts(user_id);
-- Check for sequential scans
SELECT relname, seq_scan, idx_scan
FROM pg_stat_user_tables
ORDER BY seq_scan DESC;
High Connection Count
# Check connections
supabase inspect db role-connections --linked
# Use connection pooling for serverless
# Use transaction mode, not session mode
Optimize RLS Queries
-- Avoid subqueries in hot path
-- Bad:
USING (user_id IN (SELECT user_id FROM team_members WHERE team_id = ...))
-- Good: Use security definer function
CREATE FUNCTION get_user_teams()
RETURNS SETOF uuid
LANGUAGE sql STABLE SECURITY DEFINER
AS $$ SELECT team_id FROM team_members WHERE user_id = auth.uid() $$;
USING (team_id IN (SELECT get_user_teams()))
Common Error Codes
PostgREST Errors (PGRST*)
| Code | Description | Solution |
|---|---|---|
PGRST116 |
No rows found (single expected) | Use maybeSingle() instead of single() |
PGRST205 |
Table not found in schema cache | Check table name spelling, verify table exists |
PGRST301 |
Multiple rows (single expected) | Add unique constraint or better filter |
PostgreSQL Errors
| Code | Description | Solution |
|---|---|---|
22P02 |
Invalid input syntax (e.g., bad UUID) | Validate input format before query |
23503 |
Foreign key violation | Ensure parent row exists first |
23505 |
Unique constraint violation | Use upsert or check existence |
42501 |
Insufficient privilege (RLS) | Fix RLS policies or use service role |
42703 |
Column does not exist | Check column name spelling |
42P01 |
Relation (table) doesn't exist | Check table name and schema |
08P01 |
Protocol violation | Check query syntax |
Auth Errors
| Error | Description | Solution |
|---|---|---|
Invalid API key |
Wrong or missing apikey header | Check SUPABASE_ANON_KEY |
JWT expired |
Access token expired | Call refreshSession() |
Invalid login credentials |
Wrong email/password | Verify credentials |
References
- common-errors.md - Error code reference