jpskill.com
🛠️ 開発・MCP コミュニティ

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本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
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
🪟 Windows (PowerShell)
$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. 1. 下の青いボタンを押して supabase-troubleshooting.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → supabase-troubleshooting フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

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