Use Cases (更新: 2026/6/6)

Claude CodeでSupabase連携、RLSを忘れて他人のメモが丸見えになった話

Claude CodeにSupabase実装を任せると画面は動くのにRLSが抜ける。Postgres/Auth/Storage/Edge Functionsを事故なく作る順番を実体験で解説。

Claude CodeでSupabase連携、RLSを忘れて他人のメモが丸見えになった話

「Supabaseでメモ機能、ログインつきでサクッと作っといて」

そう頼んで30分後、画面は完璧に動いていました。ログインも、メモの保存も、一覧表示も。デモも通った。ところが公開直前、別のアカウントでログインし直したら、他人のprivateメモが一覧にずらっと並んでいたんです。

背筋が凍りました。原因はコードのバグじゃありません。Row Level Security(行レベルの権限制御)を1行も書いていなかった。Claude Codeは「動くメモ機能」は完璧に作ったけれど、「自分の行しか見えない」という肝心の境界を、僕が指示しなかったから作らなかった。それだけです。

Supabaseは「動く」までがとにかく速い。だからこそ、「安全に動く」との差で事故が起きます。今日は僕が踏んだこの地雷を、あなたが踏まないための進め方を書きます。

この記事の要点

  • Supabaseの安全性の本体はアプリのif文ではなく、Postgresの**RLS(Row Level Security)**にある。ここを最初に固めないと画面が動いても本番でデータが漏れる。
  • Claude Codeに丸投げすると、RLSの欠落が「UIの成功」に隠れる。だからUIより先にSQLマイグレーションとRLSをレビューさせる順番が効く。
  • 進め方は固定:要件ファイル → SQL/RLS → 型生成 → client分離 → Server Action → 確認コマンド → 批判的レビュー。
  • owner_idはフォーム入力でなくログインユーザーから決める。StorageのパスはuserId/ファイル名に固定してSQLのpolicyと一致させる。
  • Edge Functionsも入口。Authorizationヘッダーを受け取りgetUser()で本人確認し、RLSを通す。

SupabaseとFirebaseの違いは「中身がPostgres」

Supabaseは、データベース・認証・ファイル保存・サーバー関数をまとめて借りられるBaaS(Backend as a Service/バックエンド機能をクラウドから借りる仕組み)です。手軽さはFirebaseに近い。でも決定的に違うのは、中核が**Postgres(リレーショナルDB)**だということです。

何が嬉しいか。テーブル設計に外部キーやcheck制約をちゃんと書ける。SQLで「誰がどの行を読めるか」を宣言できる。そしてその権限ルール(RLS)が、アプリのコードとは独立してDBの中で動く。クライアント側のJavaScriptがどれだけ改ざんされても、「自分の行しか読めない」「自分のフォルダにしかアップロードできない」という制約だけはDBが守り続けます。

ここがFirebaseのSecurity Rules(NoSQL向けの独自言語)との大きな分かれ道です。SupabaseならSQLの世界の知識がそのまま権限設計に効く。逆に言えば、RLSを書かなければただの素通しのDBになる、ということでもあります。

この記事ではNext.js App RouterとTypeScriptを前提に、Claude Codeへ渡す要件、スキーマとRLSのレビュー、実装、確認コマンドまでを順に並べます。認証だけ深掘りしたいならClaude Code認証実装ガイド、テーブル設計の考え方はデータベース設計ガイド、スキーマ変更の運用はDBマイグレーション自動化も合わせてどうぞ。

公式ドキュメントで先に確認する範囲

Supabaseは更新が速いので、Claude Codeに任せる前に公式を基準にします。僕がいつも最初に開くのはSupabase Docs、それとRow Level SecurityAuthEdge FunctionsStorageです。

2026年時点では、Next.jsのSSR認証では@supabase/ssrを使い、ブラウザに出すキーは新しいNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYを使う方針が推奨されています。古いanonキーが残っているプロジェクトもありますが、新規実装ではpublishable keyを前提にしたほうが安全です。Claude Codeは学習データの都合で古いキー名を書いてくることがあるので、ここは僕が必ず指定します。

まず作るもの:自分のメモを管理する小さなアプリ

今回作るのは「ログイン済みユーザーが自分のメモを作り、必要なら添付ファイルをStorageへ上げ、Edge Functionで通知を起動する」最小の実装です。学習用ですが、SaaSの管理画面、会員向けナレッジ、社内ポータルにそのまま化けます。

flowchart LR
  User["Browser"] --> Next["Next.js App Router"]
  Next --> SSR["@supabase/ssr client"]
  SSR --> Auth["Supabase Auth"]
  SSR --> DB["Postgres tables"]
  SSR --> Storage["Storage bucket"]
  Next --> Fn["Edge Function"]
  Fn --> DB
  DB --> RLS["RLS policies"]
  Storage --> StorageRLS["storage.objects policies"]

設計の急所はひとつだけ。アプリ側のif文で権限を守らないこと。Claude CodeにUIやRoute Handlerを書かせても、最後の境界はPostgresのRLSとStorage policyに置きます。冒頭の事故は、まさにこの境界をアプリ任せにした(=どこにも置かなかった)から起きました。

Claude Codeへ渡す要件ファイル

まず要件をリポジトリ内のMarkdownに落とします。会話だけでも動きますが、認証やDB権限のように失敗コストが高い作業では、ファイルを読ませる形のほうがあとでレビューしやすい。

# docs/supabase-notes-requirements.md

## Goal
Build a Supabase-backed project notes feature in Next.js App Router.

## Stack
- Next.js App Router
- TypeScript
- @supabase/supabase-js
- @supabase/ssr
- Supabase Auth, Postgres, Storage, Edge Functions

## Data model
- project_notes table
- Each note belongs to auth.users.id through owner_id
- Public notes are readable by anyone
- Private notes are readable only by the owner
- Owners can insert, update, and delete only their own notes

## Security rules
- Never expose a secret key or service role key in browser code
- Use NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY for browser and SSR clients
- Enable RLS on every public table
- Use explicit TO anon or TO authenticated in every policy
- Storage uploads must be restricted to a user-owned folder

## Claude Code workflow
1. Create SQL migration first.
2. Review RLS policies before writing UI.
3. Generate TypeScript database types.
4. Implement Supabase clients.
5. Implement server actions and upload helper.
6. Add test or manual verification commands.
7. Return a review checklist with file paths.

このファイルを作ったうえで、最初の依頼はこうします。「この要件だけを読んで、まずSQL migration案を出して。実装はまだ書かないで」。冒頭の事故のとき、僕は画面実装とRLSを同時に出させました。結果、ポリシーの欠落がUIの「動いた!」に完全に隠れた。Supabaseはデモが速いぶん、最初に権限だけを止めて見る癖が効きます。

SQLマイグレーションとRLSを先に作る

次のSQLが、メモ機能の最小スキーマです。visibilityで公開範囲を持たせ、owner_idauth.usersへ結びます。公開メモは誰でも読めますが、作成・更新・削除は本人だけ。

-- supabase/migrations/202606010001_create_project_notes.sql
create table if not exists public.project_notes (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid not null references auth.users(id) on delete cascade,
  title text not null check (char_length(title) between 1 and 120),
  body text not null default '',
  visibility text not null default 'private'
    check (visibility in ('private', 'public')),
  attachment_path text,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create index if not exists project_notes_owner_created_idx
  on public.project_notes (owner_id, created_at desc);

create or replace function public.set_updated_at()
returns trigger
language plpgsql
as $$
begin
  new.updated_at = now();
  return new;
end;
$$;

drop trigger if exists set_project_notes_updated_at on public.project_notes;

create trigger set_project_notes_updated_at
before update on public.project_notes
for each row
execute function public.set_updated_at();

alter table public.project_notes enable row level security;

create policy "Anyone can read public notes"
on public.project_notes
for select
to anon, authenticated
using (
  visibility = 'public'
  or (select auth.uid()) = owner_id
);

create policy "Owners can insert notes"
on public.project_notes
for insert
to authenticated
with check ((select auth.uid()) = owner_id);

create policy "Owners can update notes"
on public.project_notes
for update
to authenticated
using ((select auth.uid()) = owner_id)
with check ((select auth.uid()) = owner_id);

create policy "Owners can delete notes"
on public.project_notes
for delete
to authenticated
using ((select auth.uid()) = owner_id);

ここでauth.uid()を魔法のように信じないのがコツです。未ログインだとauth.uid()nullになるので、公開用とログイン用の条件を分けます。公式ガイドが勧めるとおりto authenticatedto anonを明示すると、関係ないロールでポリシー評価が走りにくくなります。

レビューでは、各policyを「誰が」「どの操作で」「既存の行に対して」「変更後の行に対して」許されるのかに分解して読みます。特にupdateはusingだけでなくwith checkも見る。usingは更新前の行を対象に、with checkは更新後の行が条件を満たすかを見ます。ここが曖昧だと、本人の行を更新する途中でowner_idや公開範囲をこっそり書き換えられる設計になります。

Storageも同じマイグレーションでバケットとポリシーを作れます。ファイル名の先頭フォルダをユーザーIDにして、RLSで縛るのが定石です。

insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values (
  'note-attachments',
  'note-attachments',
  false,
  5242880,
  array['image/png', 'image/jpeg', 'application/pdf']
)
on conflict (id) do update
set public = excluded.public,
    file_size_limit = excluded.file_size_limit,
    allowed_mime_types = excluded.allowed_mime_types;

create policy "Users can read own note attachments"
on storage.objects
for select
to authenticated
using (
  bucket_id = 'note-attachments'
  and (select auth.uid())::text = (storage.foldername(name))[1]
);

create policy "Users can upload own note attachments"
on storage.objects
for insert
to authenticated
with check (
  bucket_id = 'note-attachments'
  and (select auth.uid())::text = (storage.foldername(name))[1]
);

create policy "Users can update own note attachments"
on storage.objects
for update
to authenticated
using (
  bucket_id = 'note-attachments'
  and (select auth.uid())::text = (storage.foldername(name))[1]
)
with check (
  bucket_id = 'note-attachments'
  and (select auth.uid())::text = (storage.foldername(name))[1]
);

create policy "Users can delete own note attachments"
on storage.objects
for delete
to authenticated
using (
  bucket_id = 'note-attachments'
  and (select auth.uid())::text = (storage.foldername(name))[1]
);

ここまで書いたら、まだ画面を作らずにローカルへ反映します。@supabase/supabase-js@supabase/ssrzodを入れ、開発用にSupabase CLIとvitestを入れてから、ローカルDBを起動します。

npm install @supabase/supabase-js @supabase/ssr zod
npm install --save-dev supabase vitest
npx supabase init
npx supabase start

npx supabase db reset
npx supabase gen types typescript --local > src/lib/database.types.ts
npm run typecheck

db resetはローカル開発DBを作り直すコマンドです。本番プロジェクトに向けて実行しないでください。本番反映はCIでnpx supabase db pushやリンク済みプロジェクトへの適用手順を決めてから行います。

なお.env.localには実値を置きますが、Claude Codeのプロンプトには貼りません。共有するのは変数名と用途だけ。

NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxxxxxxxxxxxxxxxxxx

管理作業用のsecret keyや旧service role keyが要る場合も、ブラウザで読み込まれるNEXT_PUBLIC_には絶対に置きません。サーバー専用の管理処理は別のRoute Handler、ジョブ、Edge Functionに閉じ込め、RLSを迂回する理由をレビューに残します。

Supabase clientを「ブラウザ用」と「サーバー用」に分ける

Next.js App Routerでは、clientを2つに分けます。混ぜると、Cookieが同期されない・Server Componentでセッションが読めない・secret keyを誤って露出する、といった失敗が起きます。

// src/lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
import type { Database } from "@/lib/database.types";

export function createClient() {
  return createBrowserClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
  );
}
// src/lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import type { Database } from "@/lib/database.types";

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) => {
              cookieStore.set(name, value, options);
            });
          } catch {
            // Server Components cannot set cookies directly.
          }
        },
      },
    },
  );
}

Claude Codeにレビューさせるときは「createBrowserClientがサーバーファイルで使われていないか」「NEXT_PUBLIC_にsecret相当の値がないか」「cookies()をawaitしているか」を見てもらいます。Next.jsのバージョン差で壊れやすい場所なので、チェックポイントとして覚えておく価値があります。

AuthとCRUDを実装する:owner_idは絶対にフォームから受け取らない

ログインはServer Actionで扱います。実プロダクトではバリデーションやエラー整形を足しますが、基本はこの形です。

// app/login/actions.ts
"use server";

import { redirect } from "next/navigation";
import { createClient } from "@/lib/supabase/server";

export async function signIn(formData: FormData) {
  const email = String(formData.get("email") ?? "");
  const password = String(formData.get("password") ?? "");
  const supabase = await createClient();

  const { error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) {
    return { error: error.message };
  }

  redirect("/dashboard");
}

export async function signOut() {
  const supabase = await createClient();
  await supabase.auth.signOut();
  redirect("/login");
}

メモのCRUDはサーバー側に置きます。ここが一番大事で、owner_idはフォームから受け取らず、現在のログインユーザーから決めます。Claude Codeがこれをフォーム入力にしてきたら、即修正です。

// src/features/notes/actions.ts
"use server";

import { revalidatePath } from "next/cache";
import { createClient } from "@/lib/supabase/server";

type CreateNoteInput = {
  title: string;
  body: string;
  visibility?: "private" | "public";
  attachmentPath?: string | null;
};

export async function listMyNotes() {
  const supabase = await createClient();
  const {
    data: { user },
    error: userError,
  } = await supabase.auth.getUser();

  if (userError || !user) {
    throw new Error("Authentication required");
  }

  const { data, error } = await supabase
    .from("project_notes")
    .select("id,title,body,visibility,attachment_path,created_at,updated_at")
    .order("created_at", { ascending: false });

  if (error) throw error;
  return data;
}

export async function createNote(input: CreateNoteInput) {
  const supabase = await createClient();
  const {
    data: { user },
    error: userError,
  } = await supabase.auth.getUser();

  if (userError || !user) {
    throw new Error("Authentication required");
  }

  const { data, error } = await supabase
    .from("project_notes")
    .insert({
      owner_id: user.id,
      title: input.title,
      body: input.body,
      visibility: input.visibility ?? "private",
      attachment_path: input.attachmentPath ?? null,
    })
    .select("id,title,visibility")
    .single();

  if (error) throw error;
  revalidatePath("/dashboard");
  return data;
}

このコードはRLSに寄りかかっています。サーバーでgetUser()を呼ぶのはUXと入力整形のため。最終的に他人のowner_idでのinsertを拒否するのは、project_notesのRLSのほうです。アプリ側のチェックは「親切」、RLSが「最後の砦」。この二段構えを崩さないことが、冒頭の事故を二度と起こさないコツでした。

Storageへのアップロードはブラウザから直接行えます。肝心なのは、保存先をuserId/ファイル名に固定し、SQL側のStorage policyと一致させること。

// src/features/notes/upload-note-attachment.ts
"use client";

import { createClient } from "@/lib/supabase/client";

export async function uploadNoteAttachment(file: File, userId: string) {
  const supabase = createClient();
  const ext = file.name.split(".").pop()?.toLowerCase() ?? "bin";
  const path = `${userId}/${crypto.randomUUID()}.${ext}`;

  const { error } = await supabase.storage
    .from("note-attachments")
    .upload(path, file, {
      cacheControl: "3600",
      upsert: false,
      contentType: file.type,
    });

  if (error) throw error;
  return path;
}

今回のバケットはprivateです。ダウンロード時は署名付きURLやサーバー経由の配信を検討します。会員制メディアや社内ファイルは、最初からpublic bucketにしないほうが後戻りを減らせます。

Edge Functionsで通知処理を切り離す

Edge Functionsは、Deno互換の環境で動くTypeScript関数です。Webhook受信、通知、軽いAI処理、外部API連携みたいに、Next.js本体から少し離したい処理に向いています。長時間バッチや重いDB接続は別ワーカーを検討します。

npx supabase functions new notify-note-created
// supabase/functions/notify-note-created/index.ts
import { createClient } from "npm:@supabase/supabase-js@2";

Deno.serve(async (req) => {
  if (req.method !== "POST") {
    return new Response("Method not allowed", { status: 405 });
  }

  const authorization = req.headers.get("Authorization");
  if (!authorization) {
    return Response.json({ error: "Missing authorization" }, { status: 401 });
  }

  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_ANON_KEY")!,
    {
      global: {
        headers: { Authorization: authorization },
      },
    },
  );

  const {
    data: { user },
    error: userError,
  } = await supabase.auth.getUser();

  if (userError || !user) {
    return Response.json({ error: "Authentication required" }, { status: 401 });
  }

  const { noteId } = (await req.json()) as { noteId?: string };
  if (!noteId) {
    return Response.json({ error: "noteId is required" }, { status: 400 });
  }

  const { data: note, error } = await supabase
    .from("project_notes")
    .select("id,title,owner_id")
    .eq("id", noteId)
    .single();

  if (error) {
    return Response.json({ error: error.message }, { status: 404 });
  }

  return Response.json({
    ok: true,
    userId: user.id,
    note,
  });
});

ローカル実行とデプロイはCLIで。

npx supabase functions serve notify-note-created --env-file .env.local
npx supabase functions deploy notify-note-created

Edge Functionでも、Authorizationヘッダーを受け取り、getUser()で本人確認し、通常のRLSを通してDBを読む構成にします。secret keyで何でも読める関数にすると、便利な代わりにレビュー難易度が跳ね上がります。HTTPで呼べる関数は、Route Handlerと同じく「入口」だと考えてください。

実装後に必ず走らせる確認コマンド

Claude Codeに実装させたあとは、最低限この順番で確認します。

npx supabase db reset
npx supabase gen types typescript --local > src/lib/database.types.ts
npm run typecheck
npm test
npx supabase functions serve notify-note-created --env-file .env.local

SQL lintやE2Eがあるなら、ここに足します。広いビルドを毎回回す必要はありませんが、RLS変更・認証変更・Storage policy変更を含むPRでは、型生成と最小テストを省かないほうが結果的に速い。型生成を忘れると、Claude Codeが古いdatabase.types.tsに合わせたコードを書き続けて、ズレが静かに溜まっていきます。

3つの実用ユースケース

ユースケースSupabaseで使う機能Claude Codeに任せる作業人間が見るべき点
SaaSのチームメモAuth、Postgres、RLSテーブル、Server Action、一覧UIチーム境界とowner_idの混同
会員向け教材配布Auth、Storage、署名付きURLアップロードUI、配信API、監査ログpublic bucketにしていないか
イベント予約管理Postgres、Edge Functions予約テーブル、通知関数、キャンセル処理二重予約、再送、通知失敗時の復旧

どのケースでも、先に「誰がどの行を読めるか」をSQLで表現し、そのあとUIを作ります。Claude CodeはUIとAPIを一気に作るのが得意ですが、収益につながるサービスほど、境界を先に固定したほうが手戻りが減ります。

僕がSupabaseとClaude Codeでやらかした失敗3つ

正直に書きます。冒頭の「他人のメモ丸見え」以外にも、地雷はいくつも踏みました。

ひとつ目は、RLSを有効にしただけで満足したことenable row level securityを書くと、確かにポリシーがない限り誰からも読めなくなります。安心して画面に戻ったら、今度は自分のメモすら表示されない。RLSは「有効化」と「ポリシー作成」がセットです。逆に、面倒くさがってusing (true)にすると、公開してはいけない行まで全部見える。公開用と本人用は、必ず別アカウントで分けてテストします。

ふたつ目は、Storageのパスをフロント任せにしたこと。ユーザーが任意のパスを送れる作りにしたら、ポリシー設計と実際の配置がズレて、別ユーザーのフォルダに書けてしまった。userId/ランダムなファイル名という規則を決め、SQL policyとコードの両方で同じ前提に揃えてからは起きていません。

みっつ目は、service role keyをClaude Codeの作業範囲に混ぜたこと。管理者キーはRLSを迂回できます。デバッグのつもりで.env.localを読ませたり、ログに環境変数を出させたりしたら、その瞬間に「最後の砦」が無意味になる。管理キーは人間がサーバー専用の場所に閉じ込め、AIの目には触れさせない。これは徹底しています。

Claude Codeに渡す批判的レビュー用プロンプト

実装が一段落したら、Claude Codeに次のレビューを依頼します。コツは、実装させたのと同じ会話で続けてレビューさせるより、差分が落ち着いたあとで「批判的に見て」と依頼を分けること。同じ会話だと、AIは自分の書いたコードを甘く採点しがちです。

Review only the Supabase integration.
Check these points:
- public tables have RLS enabled
- every policy has an explicit TO role
- auth.uid() is used only in RLS-safe expressions
- browser code uses only NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
- no secret key or service role key appears in client code, logs, tests, or docs
- owner_id is derived from the authenticated user, not from form input
- Storage paths match storage.objects policies
- migration and generated database types are in sync
- Edge Functions validate Authorization and use RLS-aware clients
Return findings with file paths and line numbers.

人間側では、ログイン前の公開メモ一覧、ログイン後の自分のprivateメモ、他人のメモIDを指定した更新の失敗、別ユーザーのStorageパスへのアップロード失敗、Edge Functionの未認証リクエストの失敗を、実際に手で叩いて確認します。ここまでやって初めて、概要記事ではなく実務記事になります。

よくある質問

Q. SupabaseとFirebase、どちらを選べばいい? A. SQLや外部キー、複雑なクエリを使いたい・将来データを正規化したいならSupabase(Postgres)。とにかく素早くNoSQLで始めたいならFirebase。権限設計の考え方も違い、SupabaseはSQLのRLS、FirebaseはSecurity Rulesです。Firebase側の事故防止はFirebase開発でデータ流出を防ぐ記事にまとめました。

Q. RLSを有効にしたら、何も表示されなくなりました。 A. 正常な反応です。RLSは有効化した時点で「ポリシーがなければ全拒否」になります。select用のポリシー(例:本人の行 or 公開行を許可)を追加してください。enable row level securitycreate policyはセットだと覚えておくと事故りません。

Q. anonキーとpublishableキー、どっちを使うべき? A. 2026年時点の新規実装ではNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYが推奨です。anonキーは既存プロジェクトに残っていることがありますが、新規ならpublishable keyに揃えるほうが将来の移行コストが小さくなります。

Q. service role keyはどこで使えばいい? A. RLSを迂回できる管理者キーなので、ブラウザやClaude Codeのプロンプトには絶対に出しません。サーバー専用のRoute Handler、バッチ、Edge Functionなど人間が管理する場所だけに閉じ込め、なぜRLSを迂回するのかをレビューに必ず残します。

Q. Edge Functionでも本人確認は必要? A. 必要です。HTTPで呼べる関数はRoute Handlerと同じ入口です。Authorizationヘッダーを受け取り、getUser()で本人を確認し、RLSを通してDBを読む。secret keyで全件読めるショートカットは作らないでください。

実際に試した結果

冒頭の「他人のメモ丸見え」事件のあと、僕はClaude Codeへの頼み方を変えました。いちばん効いたのは、UIより先にRLSのマイグレーションをレビューさせる順番です。

最初に画面を作らせると、owner_idをフォーム由来にする差分がしれっと混ざり、あとから直すのに時間がかかる。逆に、要件ファイル → SQL/RLS → 型生成 → Server Action、と分けて出させると、Claude Codeの出力も僕のレビューも一回が小さくなり、公開前に見るべき点がはっきりしました。賢いAIに丸投げするより、「自分の行しか見えない」という境界をSQLで先に固定する。遠回りに見えて、これがいちばん事故らない、というのが今の実感です。

SupabaseとClaude Codeはプロトタイプの速度を上げる一方で、RLS・認証・Storage policy・マイグレーション運用の理解が浅いと、そのまま本番事故の入口になります。自社アプリにAuthや会員向けファイル配信を入れるなら、Claude Code導入相談・研修で今の構成と不安な箇所を一度棚卸ししてから進めるのが安全です。手を動かして試したい人は教材一覧も置いておきます。

#Claude Code #Supabase #PostgreSQL #RLS #認証 #Edge Functions
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。