studio-best-practices について
Supabase Studio の TypeScript と React を扱う開発者向けのコーディングガイドです。コンポーネント構成、データ取得、状態管理、パフォーマンス、型付け、テストの規約を説明します。
主な機能
- 01説明的なブール値の命名
- 02コンポーネント構成の指針
- 03TanStack Query によるデータ取得
- 04ローカル状態とフォームの規約
- 05境界検証とテストの指針
活用シーン
- 01Studio コンポーネントの整理
- 02クエリの読み込み・エラー状態の処理
- 03フォーム状態とイベントハンドラーの構成
- 04テスト向けの純粋関数の抽出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ supabase/ supabase --skill studio-best-practicesインストール後、Claude Code で/studio-best-practicesを入力して読み込まれたか確認します。
詳細
Studio のベストプラクティス
apps/studio/**/*.{ts,tsx} に適用されます。
ブール値の命名
説明的な接頭辞を使用し、別途保存するのではなく既存の状態から導出します。
is— 状態/識別:isLoading、isPaused、isNewRecordhas— 所有:hasPermission、hasDatacan— 能力:canUpdateColumns、canDeleteshould— 条件付き動作:shouldFetch、shouldRender
複雑な条件は名前付き変数に抽出します。
// ❌ inline multi-condition
{
!isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading && <Button />
}
// ✅ named variable
const canShowAddButton =
!isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading
{
canShowAddButton && <Button />
}
ブール値は導出し、保存しないでください。
// ❌ stored derived state
const [isFormValid, setIsFormValid] = useState(false)
useEffect(() => {
setIsFormValid(name.length > 0 && email.includes('@'))
}, [name, email])
// ✅ derived
const isFormValid = name.length > 0 && email.includes('@')
コンポーネントの構成
複合コンポーネントとコンポジションのパターンについては、vercel-composition-patterns スキルを参照してください。
コンポーネントは 200~300 行未満に保ちます。次のような場合は分割してください。
- 複数の独立した UI セクションがある
- 条件付きレンダリングが複雑である
- 関連性のない
useState呼び出しが複数ある - ひと目で理解しにくい
サブコンポーネントは親と同じディレクトリにまとめます。再エクスポート用のバレルファイルは避けてください。
繰り返し使う JSX パターンは、小さなコンポーネントに抽出します。
データ取得
すべてのデータ取得には TanStack Query(React Query)を使用します。クエリ/ミューテーションのパターンについては studio-queries スキルを、エラー表示の規約については studio-error-handling スキルを参照してください。
ローディング/エラー/成功のパターン
トップレベル:
const { data, error, isLoading, isError, isSuccess } = useQuery(...)
if (isLoading) return <GenericSkeletonLoader />
if (isError) return <AlertError error={error} subject="Failed to load data" />
if (isSuccess && data.length === 0) return <EmptyState />
return <DataDisplay data={data} />
早期リターンを使い、深くネストした条件分岐は避けます。
インライン:
<div>
{isLoading && <InlineLoader />}
{isError && <InlineError error={error} />}
{isSuccess && data.length === 0 && <EmptyState />}
{isSuccess && data.length > 0 && <DataDisplay data={data} />}
</div>
状態管理
状態は可能な限りローカルに保ち、必要な場合にのみリフトアップします。
複数の useState 呼び出しを使うのではなく、関連するフォームの状態は react-hook-form でまとめます。フォームのレイアウトとコンポーネントの規約については、studio-ui-patterns スキルを参照してください。
// ❌ multiple related useState
const [name, setName] = useState('')
const [email, setEmail] = useState('')
// ✅ grouped with react-hook-form
const form = useForm<FormValues>({ defaultValues: { name: '', email: '' } })
カスタムフック
複雑なロジックや再利用可能なロジックはフックに抽出します。配列ではなくオブジェクトを返します。
// ❌ array return (hard to extend)
return [value, toggle]
// ✅ object return
return { value, toggle, setTrue, setFalse }
イベントハンドラー
- プロパティのコールバック:
on接頭辞(onClose、onSave) - 内部ハンドラー:
handle接頭辞(handleSubmit、handleCancel)
メモ化された子コンポーネントに渡すハンドラーには useCallback を使用し、不要なインラインのアロー関数は避けてください。
条件付きレンダリング
// Simple show/hide
<>{isVisible && <Component />}</>
// Binary choice
<>{isLoading ? <Spinner /> : <Content />}</>
// Multiple conditions — use early returns, not nested ternaries
if (isLoading) return <Spinner />
if (isError) return <Error />
return <Content />
パフォーマンス
本当にコストの高い計算(想定ではなく、計測済みのもの)には useMemo を使用します。すべてをラップするのではなく、計測によって問題が確認された場合、または値をメモ化された子コンポーネントに渡す場合にのみ最適化してください。
TypeScript
プロパティのインターフェースを明示的に定義します。複雑な状態には判別可能なユニオンを使用します。
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
as any/as Type による型アサーションは避けてください。境界では zod を使って検証します。
// ❌ type cast
const user = apiResponse as User
// ✅ zod parse
const user = userSchema.parse(apiResponse)
// or safe:
const result = userSchema.safeParse(apiResponse)
テスト
ロジックを .utils.ts の純粋関数に抽出し、網羅的にテストします。テスト戦略全体と判断ツリーについては、studio-testing スキルを参照してください。