Intégrer Supabase dans une application React (Vite, CRA ou standalone) demande une approche différente de Next.js. Ce guide couvre les patterns recommandés : hooks personnalisés, contexte Auth, React Query, et gestion optimiste de l’état.
Pour comprendre les fondations, consulte notre guide complet Supabase. Pour l’intégration SSR/SSG, voir Supabase avec Next.js. Pour l’authentification, voir Supabase Auth. Pour les données temps réel, voir Supabase RLS. Pour les JWT, voir Supabase JWT.
Installation
# Créer un projet React avec Vite
npm create vite@latest mon-app -- --template react-ts
cd mon-app
# Installer Supabase
npm install @supabase/supabase-js
Initialiser le client
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from './types/supabase'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true
}
})
Context Auth Global
// src/contexts/AuthContext.tsx
import { createContext, useContext, useEffect, useState } from 'react'
import type { Session, User } from '@supabase/supabase-js'
import { supabase } from '@/lib/supabase'
type AuthContextType = {
user: User | null
session: Session | null
loading: boolean
signOut: () => Promise<void>
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [session, setSession] = useState<Session | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
setUser(session?.user ?? null)
setLoading(false)
})
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session)
setUser(session?.user ?? null)
setLoading(false)
}
)
return () => subscription.unsubscribe()
}, [])
const signOut = async () => {
await supabase.auth.signOut()
}
return (
<AuthContext.Provider value={{ user, session, loading, signOut }}>
{children}
</AuthContext.Provider>
)
}
export const useAuth = () => useContext(AuthContext)
Hooks Personnalisés Supabase
useQuery : Fetch de données
export function useSupabaseQuery<T>(
queryFn: () => Promise<any>
) {
const [data, setData] = useState<T | null>(null)
const [error, setError] = useState<any>(null)
const [loading, setLoading] = useState(true)
const [trigger, setTrigger] = useState(0)
useEffect(() => {
setLoading(true)
queryFn().then(({ data, error }) => {
setData(data)
setError(error)
setLoading(false)
})
}, [trigger])
return { data, error, loading, refetch: () => setTrigger(t => t + 1) }
}
useRealtime : Données en temps réel
export function useRealtime<T extends { id: string }>(
table: string,
initialData: T[] = []
) {
const [data, setData] = useState<T[]>(initialData)
useEffect(() => {
const channel = supabase
.channel(`${table}-realtime`)
.on(
'postgres_changes',
{ event: '*', schema: 'public', table },
(payload) => {
setData(current => {
switch (payload.eventType) {
case 'INSERT': return [payload.new as T, ...current]
case 'UPDATE': return current.map(item =>
item.id === payload.new.id ? payload.new as T : item
)
case 'DELETE': return current.filter(item => item.id !== payload.old.id)
default: return current
}
})
}
)
.subscribe()
return () => { supabase.removeChannel(channel) }
}, [table])
return data
}
Intégration avec TanStack Query (React Query)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function usePosts() {
return useQuery({
queryKey: ['posts'],
queryFn: async () => {
const { data, error } = await supabase
.from('posts')
.select('*, profiles(username)')
.order('created_at', { ascending: false })
if (error) throw error
return data
},
staleTime: 1000 * 60 * 5,
})
}
export function useCreatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (post: { title: string; content: string }) => {
const { data, error } = await supabase
.from('posts')
.insert(post)
.select()
.single()
if (error) throw error
return data
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
}
👉 Articles du guide Supabase
Cet article fait partie du Guide Complet Supabase 2026. Retrouvez les autres articles :
- ✅ Supabase : Le Guide Complet 2026
- ✅ Supabase vs Firebase : Comparatif Complet 2026
- ✅ Installer Supabase en Local avec Docker
- ✅ Supabase Pricing : Quel Plan Choisir en 2026
- ✅ Architecture Supabase : Postgres, GoTrue, Realtime et Storage
- ✅ Créer et Structurer sa Base de Données Supabase
- ✅ Supabase RLS : Sécuriser ses Données avec Row Level Security
- ✅ Supabase SQL : Triggers, Fonctions et Procédures Stockées
- ✅ Supabase Migrations : Gérer l’Évolution de son Schéma
- ✅ Supabase Auth : Email, OAuth et Magic Links
- ✅ Supabase Rôles et Permissions
- ✅ Supabase JWT : Comprendre les Tokens
- ✅ Supabase avec Next.js : App Router
- 📅 API REST Supabase — à paraître le 24/06
- 📅 Edge Functions — à paraître le 26/06
- 📅 Realtime Supabase — à paraître le 28/06
- 📅 Storage Supabase — à paraître le 30/06
- 📅 Vector & pgvector — à paraître le 02/07
- 📅 Supabase MCP — à paraître le 04/07
- 📅 Déploiement en production — à paraître le 06/07
- 📅 CLI Supabase — à paraître le 08/07

