Bonnes pratiques et production
Architecture, migrations, monitoring, types auto-générés et checklist de mise en production.
10.1Migrations avec le CLI
Ne modifie jamais le schéma directement dans le dashboard en production. Utilise les migrations SQL versionnées :
Workflow avec les migrationsbash
# Créer une nouvelle migration
supabase migration new add-articles-table
# → supabase/migrations/20240101120000_add-articles-table.sql
# Écrire le SQL de la migration
# (le fichier est créé vide, à toi de le remplir)
# Appliquer les migrations en local
supabase db reset # Recrée la DB locale depuis les migrations
# Pousser les migrations en production
supabase db pushsupabase/migrations/20240101120000_add-articles-table.sqlbash
-- Migration : créer la table articles
CREATE TABLE IF NOT EXISTS articles (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
title TEXT NOT NULL,
content TEXT,
published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
-- RLS
ALTER TABLE articles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users can read published articles"
ON articles FOR SELECT
USING (published = true);
CREATE POLICY "users can manage own articles"
ON articles FOR ALL
USING (auth.uid() = user_id);10.2Types TypeScript auto-générés
Générer les types depuis le schémabash
# Générer les types TypeScript
supabase gen types typescript --linked > lib/types/database.ts
# Ou depuis une DB locale
supabase gen types typescript --local > lib/types/database.tslib/types/database.ts (extrait généré)typescript
export type Database = {
public: {
Tables: {
articles: {
Row: {
id: string
user_id: string
title: string
content: string | null
published: boolean
created_at: string
}
Insert: {
id?: string
user_id: string
title: string
content?: string | null
published?: boolean
created_at?: string
}
Update: {
id?: string
user_id?: string
title?: string
content?: string | null
published?: boolean
created_at?: string
}
}
}
}
}Utiliser les types dans le clienttypescript
import { createBrowserClient } from '@supabase/ssr'
import type { Database } from '@/lib/types/database'
export function createClient() {
return createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
)
}
// Maintenant TypeScript connaît le schéma :
const { data } = await supabase.from('articles').select('*')
// data est typé : Database['public']['Tables']['articles']['Row'][]10.3Architecture recommandée
Structure de projet Supabase + Next.jsbash
projet/
├── supabase/
│ ├── config.toml # Config du projet Supabase
│ ├── migrations/ # Migrations SQL versionnées
│ │ ├── 20240101_init.sql
│ │ └── 20240115_add_articles.sql
│ ├── functions/ # Edge Functions
│ │ └── stripe-webhook/
│ │ └── index.ts
│ └── seed.sql # Données de test
│
├── lib/
│ ├── supabase/
│ │ ├── client.ts # createBrowserClient
│ │ └── server.ts # createServerClient
│ └── types/
│ └── database.ts # Types auto-générés
│
├── middleware.ts # Refresh JWT
└── .env.local # Variables d'environnement10.4Sécurité en production
| Checklist | Détail |
|---|---|
| RLS activé sur toutes les tables | Jamais de table sans RLS en production |
| anon key = côté client uniquement | Elle est publique, RLS la protège |
| service_role key = serveur uniquement | Bypass RLS, ne jamais exposer côté client |
| Tester les policies | Vérifier qu'un user ne peut pas lire/modifier les données d'un autre |
| Email confirmé obligatoire | Dashboard → Auth → Settings → Confirm email |
| Rate limiting | Auth rate limits dans les settings du dashboard |
10.5Monitoring et logs
Dashboard Supabasebash
Dashboard :
├── Logs → API Logs # Toutes les requêtes API
├── Logs → Auth Logs # Connexions, inscriptions, erreurs
├── Logs → Database Logs # Requêtes SQL lentes
├── Reports # Usage, quotas, alertes
└── SQL Editor # Exécuter des requêtes manuellesLogger les erreurs côté applicationtypescript
// Wrapper pour logger les erreurs Supabase
async function safeQuery<T>(
query: Promise<{ data: T | null; error: { message: string } | null }>
): Promise<T | null> {
const { data, error } = await query
if (error) {
console.error('[Supabase Error]', error.message)
return null
}
return data
}
// Utilisation
const articles = await safeQuery(
supabase.from('articles').select('*').eq('published', true)
)10.6Environnements séparés
Deux projets Supabasebash
# Bonne pratique : un projet par environnement
Supabase Dashboard :
├── mon-app-dev # Développement (données de test)
└── mon-app-prod # Production (données réelles)
# .env.local (dev)
NEXT_PUBLIC_SUPABASE_URL=https://xxxx-dev.supabase.co
# .env.production (prod)
NEXT_PUBLIC_SUPABASE_URL=https://xxxx-prod.supabase.co
# Les migrations sont les mêmes, appliquées aux deux
supabase link --project-ref projet-dev
supabase db push
supabase link --project-ref projet-prod
supabase db push