Multi-Tenant Next.js avec Supabase RLS : Guide de Survie en Production
Pourquoi Supabase RLS pour la Multi-Tenancy
Votre application SaaS lance avec trois locataires. Les politiques Supabase RLS semblent solides—vérifications team_id sur chaque table, service role verrouillé, middleware transmettant les bonnes réclamations JWT. Puis le locataire quatre embarque, et un développeur voit le tableau de bord d'une autre entreprise pendant onze secondes avant que votre Slack explose. Des bases de données séparées par locataire auraient contourné cela, bien sûr—mais à 47 $/mois par instance Postgres et zéro capacité à exécuter des analyses inter-locataires, cette mathématique meurt vite. Les schémas partagés semblent plus sensés jusqu'à votre première migration qui verrouille 8 000 lignes sur douze locataires et les tickets d'assistance s'accumulent. L'architecture qui survit réellement en production : tables partagées avec Supabase Row Level Security. PostgreSQL native, vérifications de politiques en dessous de la milliseconde, et vous écrivez la logique d'isolation une fois. Mais RLS en production comporte sept arêtes vives que nous n'avons pas vues en staging—voici chacune d'elles, et les motifs qui les corrigent.
Pourquoi cela importe-t-il même ? Simple. Votre filtrage de données se produit au niveau de la base de données. Si vous vous trompez sur une clause WHERE dans votre route API Next.js, vous ne restez pas debout la nuit en pensant aux violations de données, car la base de données elle-même est votre filet de sécurité. Et franchement, à l'époque actuelle, ce n'est pas un luxe—c'est une nécessité.
Mais ne nous trompons pas. RLS ajoute des frais généraux à vos requêtes, complique le débogage et peut vous piéger lors des migrations. Alors, comment les différentes approches de multi-tenancy se comparent-elles ?
| Approche | Niveau d'isolation | Coût | Complexité opérationnelle | Performance des requêtes |
|---|---|---|---|---|
| Base de données par locataire | Complète | Élevé (50-200 $/loca/mois) | Très élevée | Meilleure |
| Schéma par locataire | Fort | Moyen | Élevée (migrations) | Bon |
| Tables partagées + RLS | Au niveau des lignes | Faible | Moyen | Bon (avec réserves) |
| Filtrage au niveau de l'app | Aucun | Le plus faible | Faible | Meilleur |
Pour la plupart des produits SaaS avec moins de 10 000 locataires, les tables partagées avec RLS offrent le meilleur rapport qualité-prix. C'est ce que nous explorons ici.

Motifs d'architecture : Partagé vs Isolé
Avant même de penser à écrire du code, vous devez choisir votre stratégie de résolution de locataire. Dans la nature, vous rencontrerez surtout deux créatures :
Tenancy Basé sur les Sous-domaines
Vous avez déjà vu tenant-slug.yourapp.com ? Bienvenue au motif le plus courant pour les SaaS B2B. C'est élégant, professionnel, et rend la résolution de locataire dans le middleware une promenade de santé.
Tenancy Basé sur le Chemin
Celui-ci est votre /org/tenant-slug/dashboard classique. Plus facile à mettre en place puisqu'il n'y a pas de DNS wildcard, et cela fonctionne sur des plates-formes comme Vercel sans domaines personnalisés. Mais honnêtement : cela ressemble à porter des chaussettes avec des sandales. Nous recommandons généralement la tenancy basée sur les sous-domaines pour les applications B2B de production et la tenancy basée sur le chemin pour les outils internes ou les MVP. Vous voulez changer plus tard ? Vous maudirez votre moi passé—changer ces motifs n'est pas une blague.
Configuration du Schéma de Locataire
Voici un motif de schéma qui ne nous a pas laissés tomber dans trois déploiements en production différents :
-- Table locataire de base
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
plan TEXT NOT NULL DEFAULT 'free',
created_at TIMESTAMPTZ DEFAULT now(),
settings JSONB DEFAULT '{}'
);
-- Table de jonction d'appartenance
CREATE TABLE memberships (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(user_id, org_id)
);
-- Exemple de table étendue au locataire
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE NOT NULL,
name TEXT NOT NULL,
description TEXT,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT now()
);
-- Index sur org_id — vous en aurez besoin sur TOUTE table étendue au locataire
CREATE INDEX idx_projects_org_id ON projects(org_id);
CREATE INDEX idx_memberships_user_id ON memberships(user_id);
CREATE INDEX idx_memberships_org_id ON memberships(org_id);
La table memberships est le ciment qui tient tout ensemble. Toutes vos politiques RLS y pointeront comme si c'était leur cousin préféré. Les utilisateurs peuvent rejoindre plusieurs organisations, et leurs rôles dictent ce qu'ils peuvent ou ne peuvent pas faire. Et voici une petite pépite de sagesse : toujours—sérieusement, toujours—indexer org_id sur chaque table étendue au locataire. Sinon, regardez vos requêtes ramper comme de la mélasse une fois que vous nagez dans les données. Nous avons été pris au dépourvu par cela quand le tableau de bord d'un client a plongé de 50 ms à 8 secondes avec 100 000 lignes. Leçon apprise.
Politiques RLS qui s'échelonnent Réellement
C'est ici que les tutoriels s'inclinent généralement, vous laissant échoué. Ils vous balancent auth.uid() = user_id et disent : « Bonne chance ! » Mais RLS multi-tenant ne peut pas être réduit comme cela.
Le Motif de Fonction d'Aide
Pourquoi encombrer chaque politique avec des vérifications d'appartenance ? Utilisez plutôt une fonction d'aide :
-- Aide : vérifier si l'utilisateur actuel est membre d'une org
CREATE OR REPLACE FUNCTION public.is_member_of(org UUID)
RETURNS BOOLEAN AS $$
SELECT EXISTS (
SELECT 1 FROM memberships
WHERE user_id = auth.uid()
AND org_id = org
);
$$ LANGUAGE sql SECURITY DEFINER STABLE;
-- Aide : obtenir le rôle de l'utilisateur dans une org
CREATE OR REPLACE FUNCTION public.get_role_in(org UUID)
RETURNS TEXT AS $$
SELECT role FROM memberships
WHERE user_id = auth.uid()
AND org_id = org
LIMIT 1;
$$ LANGUAGE sql SECURITY DEFINER STABLE;
Pourquoi SECURITY DEFINER ? Parce que la fonction s'exécute avec les privilèges du créateur, contournant RLS sur la table memberships. Sans cela, vous risquez de tomber dans un terrier de dépendance circulaire où RLS sur memberships plante les vérifications d'appartenance sur lesquelles d'autres tables s'appuient.
Et la partie STABLE ? Elle signale au planificateur de requête que la sortie de la fonction reste cohérente pour la même entrée lors d'une seule requête, permettant une mise en cache agréable. Tenté d'utiliser IMMUTABLE ? Ne le faites pas. L'appartenance peut basculer entre les transactions.
Politiques pour les Tables Étendues au Locataire
Considérons quelques politiques pour notre table projects :
-- Activer RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- SELECT : les membres peuvent voir les projets dans leurs orgs
CREATE POLICY "Members can view org projects"
ON projects FOR SELECT
USING (public.is_member_of(org_id));
-- INSERT : les admins et propriétaires peuvent créer des projets
CREATE POLICY "Admins can create projects"
ON projects FOR INSERT
WITH CHECK (
public.get_role_in(org_id) IN ('owner', 'admin')
);
-- UPDATE : les admins et propriétaires peuvent mettre à jour les projets
CREATE POLICY "Admins can update projects"
ON projects FOR UPDATE
USING (public.is_member_of(org_id))
WITH CHECK (
public.get_role_in(org_id) IN ('owner', 'admin')
);
-- DELETE : seuls les propriétaires peuvent supprimer les projets
CREATE POLICY "Owners can delete projects"
ON projects FOR DELETE
USING (
public.get_role_in(org_id) = 'owner'
);
Politiques pour la Table Memberships Elle-même
Celle-ci est délicate. La table memberships obtient son propre RLS, mais elle ne peut pas utiliser les fonctions d'aide car elles, à leur tour, interrogent memberships—files d'attente aux références circulaires :
ALTER TABLE memberships ENABLE ROW LEVEL SECURITY;
-- Les utilisateurs peuvent voir les appartenances dans les orgs auxquels ils appartiennent
CREATE POLICY "Users can view org memberships"
ON memberships FOR SELECT
USING (
org_id IN (
SELECT org_id FROM memberships WHERE user_id = auth.uid()
)
);
-- Seuls les propriétaires peuvent ajouter des membres
CREATE POLICY "Owners can add members"
ON memberships FOR INSERT
WITH CHECK (
org_id IN (
SELECT org_id FROM memberships
WHERE user_id = auth.uid() AND role = 'owner'
)
);
Oui, il y a une sous-requête sur la même table. Et oui, PostgreSQL l'exécute impeccablement. La sous-requête vérifie votre propre appartenance, inaffectée par la politique en cours de définition puisque RLS n'enveloppe que la requête externe. Mais testez cela—sérieusement, vous ne voulez pas découvrir un bogue en production.

Middleware Next.js pour la Résolution de Locataire
Avec Next.js 15 et le superbe App Router, le middleware s'exécutant à la périphérie est le gestionnaire d'immeubles parfait pour la résolution de locataire. Voici notre motif de confiance pour les configurations basées sur les sous-domaines :
// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const PUBLIC_ROUTES = ['/login', '/signup', '/invite'];
export async function middleware(request: NextRequest) {
const hostname = request.headers.get('host') || '';
const currentHost = hostname.split('.')[0];
// Ignorer le domaine principal et localhost
const isMainDomain = currentHost === 'app' || currentHost === 'www' || currentHost === 'localhost:3000';
let response = NextResponse.next({
request: { headers: request.headers },
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
request.cookies.set(name, value);
response.cookies.set(name, value, options);
});
},
},
}
);
const { data: { user } } = await supabase.auth.getUser();
if (!isMainDomain) {
response.headers.set('x-tenant-slug', currentHost);
if (!user && !PUBLIC_ROUTES.some(r => request.nextUrl.pathname.startsWith(r))) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|api/webhooks).*)'],
};
L'en-tête x-tenant-slug vaut son poids en or. Utilisez-le pour laisser vos Composants Serveur et routes API savoir avec quel locataire ils traitent. Si vous collaborez avec nous sur un projet Next.js, la configuration de ceci est notre priorité du jour un.
Flux d'authentification dans les Applications Multi-Tenant
Supabase Auth joue la neutralité dans le jeu de la multi-tenancy. Les utilisateurs existent dans une sphère mondiale—les relations de locataire sont votre puzzle à résoudre. Voici notre plan de jeu :
- L'utilisateur s'inscrit : Créer un utilisateur auth, construire une organisation, et inventer une appartenance avec un rôle 'propriétaire'.
- L'utilisateur est invité : L'admin élabore une invitation en attente, un nouvel utilisateur rejoint via le lien d'invitation, et puff—une appartenance apparaît avec le rôle spécifié.
- L'utilisateur se connecte : Extraire le locataire du sous-domaine, confirmer l'appartenance, l'escorter vers son tableau de bord.
// app/api/auth/signup/route.ts
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { email, password, orgName, orgSlug } = await request.json();
const supabase = await createClient();
// Inscrire l'utilisateur
const { data: authData, error: authError } = await supabase.auth.signUp({
email,
password,
});
if (authError) return NextResponse.json({ error: authError.message }, { status: 400 });
// Utiliser un client avec rôle de service pour la création d'org (contourne RLS)
const adminClient = createAdminClient();
const { data: org, error: orgError } = await adminClient
.from('organizations')
.insert({ name: orgName, slug: orgSlug })
.select()
.single();
if (orgError) return NextResponse.json({ error: orgError.message }, { status: 400 });
// Créer l'appartenance propriétaire
await adminClient
.from('memberships')
.insert({
user_id: authData.user!.id,
org_id: org.id,
role: 'owner',
});
return NextResponse.json({ org });
}
Notez que nous comptons sur un client avec rôle de service lors de l'inscription. L'utilisateur n'a pas encore d'appartenances, donc RLS le laisserait sans aide pour la création d'organisations. C'est l'un de ces problèmes d'amorçage classiques—votre clé de rôle de service sera votre baguette magique.
Et je ne peux pas assez souligner ceci : Ne jamais, jamais exposer votre clé de rôle de service au client. C'est strictement pour le code côté serveur.
Composants Serveur et RLS : Le Problème SSR
Les Composants Serveur de Next.js 15 sont liés au serveur, renforçant le jeu de sécurité. Mais il y a un problème lors de l'utilisation de Supabase RLS : vous devez fournir la session de l'utilisateur au client Supabase pour que les politiques RLS sachent qui est à la table.
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
} catch {
// Cela peut échouer dans les Composants Serveur (lecture seule)
// Le middleware gère le rafraîchissement des cookies
}
},
},
}
);
}
// app/[orgSlug]/projects/page.tsx
import { createClient } from '@/lib/supabase/server';
import { headers } from 'next/headers';
export default async function ProjectsPage() {
const supabase = await createClient();
const headersList = await headers();
const tenantSlug = headersList.get('x-tenant-slug');
// Obtenir l'ID org à partir du slug
const { data: org } = await supabase
.from('organizations')
.select('id')
.eq('slug', tenantSlug)
.single();
if (!org) return <div>Organization not found</div>;
// RLS filtre automatiquement — retourne uniquement les projets
// où l'utilisateur actuel a une appartenance
const { data: projects } = await supabase
.from('projects')
.select('*')
.eq('org_id', org.id)
.order('created_at', { ascending: false });
return (
<div>
{projects?.map(project => (
<ProjectCard key={project.id} project={project} />
))}
</div>
);
}
Voici le truc : même si quelqu'un truque org_id dans la requête, RLS ne bougera pas. Il bloque l'accès aux projets à moins que l'utilisateur ne soit membre. Techniquement, .eq('org_id', org.id) est redondant pour la sécurité—RLS gère cela—mais c'est bon pour la performance et la lisibilité.
Optimisation des Performances et Pièges Courants
Le Problème de Requête N+1 RLS
Chaque vérification de politique RLS lance une sous-requête. Se brancher sur une vérification de politique 10-lignes quand vous visez 100 lignes signifie 100 tours de recherche d'appartenance. Heureusement, PostgreSQL est assez intelligent pour mettre en cache—mais il y a des frais généraux.
Correction : Utilisez STABLE sur les fonctions d'aide (comme nous l'avons décrit). Aussi, pensez à dénormaliser org_id dans les réclamations JWT :
-- Crochet JWT personnalisé (Tableau de bord Supabase > Auth > Hooks)
CREATE OR REPLACE FUNCTION public.custom_access_token_hook(event JSONB)
RETURNS JSONB AS $$
DECLARE
org_ids UUID[];
BEGIN
SELECT array_agg(org_id) INTO org_ids
FROM memberships
WHERE user_id = (event->>'user_id')::UUID;
event := jsonb_set(
event,
'{claims,org_ids}',
to_jsonb(org_ids)
);
RETURN event;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Alors votre politique RLS devient :
CREATE POLICY "Members can view"
ON projects FOR SELECT
USING (
org_id = ANY(
(SELECT array(SELECT jsonb_array_elements_text(
auth.jwt()->'org_ids'
))::UUID[])
)
);
Ceci écrase complètement la recherche de la table d'appartenance. Les IDs org arrivent droit du JWT. Réserve : Les réclamations JWT sont horodatées à la connexion. Changez l'appartenance de quelqu'un, et il devra se réauthentifier pour synchroniser les réclamations. Généralement, c'est tout à fait gérable—gardez-le simplement dans vos docs.
Mise en commun des Connexions
Supabase propose la mise en commun des connexions via PgBouncer. Si vous lancez en direct avec Next.js sur Vercel, souvenez-vous : URL de pooler pour les routes API et composants serveur.
# Pour les opérations régulières (en commun)
DATABASE_URL=postgres://user:pass@db.project.supabase.co:6543/postgres
# Pour les migrations uniquement (direct)
DIRECT_URL=postgres://user:pass@db.project.supabase.co:5432/postgres
Toute personne sur le Supabase Pro de Supabase pour 25 $ par mois reçoit 200 connexions simultanées via le pooler. Pour la plupart des applications SaaS timides de 1000 utilisateurs concurrents, c'est plus que suffisant.
Index dont vous avez Absolument Besoin
Voici l'ensemble d'index à force brute pour une configuration multi-tenant :
-- Sur chaque table étendue au locataire
CREATE INDEX idx_{table}_org_id ON {table}(org_id);
-- Index composites pour les requêtes courantes
CREATE INDEX idx_projects_org_created ON projects(org_id, created_at DESC);
-- Memberships — fortement interrogé par RLS
CREATE INDEX idx_memberships_user_org ON memberships(user_id, org_id);
CREATE INDEX idx_memberships_org_role ON memberships(org_id, role);
EXPLAIN ANALYZE—le meilleur ami d'un développeur. Voyez comment vos requêtes se comportent avec RLS à bord. Vous pourriez avoir une mauvaise surprise sur ce que le planificateur décide de faire sans les bons index.
Test des Politiques RLS
Tout le monde saute pardessus, pourtant c'est votre meilleur filet de sécurité contre les fuites de données. Nous testons les politiques RLS directement en SQL :
-- Test en tant qu'utilisateur spécifique
SET request.jwt.claims = '{"sub": "user-uuid-here", "role": "authenticated"}';
SET role = 'authenticated';
-- Ceci devrait retourner uniquement les projets auxquels l'utilisateur a accès
SELECT * FROM projects;
-- Ceci devrait échouer (utilisateur n'est pas membre de cette org)
INSERT INTO projects (org_id, name) VALUES ('other-org-uuid', 'Sneaky Project');
-- Réinitialiser
RESET role;
Et n'oublions pas pgTAP pour les politiques critiques :
BEGIN;
SELECT plan(3);
-- Configurer le contexte de test en tant qu'utilisateur A (membre de l'org 1)
SET LOCAL request.jwt.claims = '{"sub": "user-a-uuid"}';
SET LOCAL role = 'authenticated';
SELECT is(
(SELECT count(*) FROM projects WHERE org_id = 'org-1-uuid')::INTEGER,
5,
'User A sees 5 projects in their org'
);
SELECT is(
(SELECT count(*) FROM projects WHERE org_id = 'org-2-uuid')::INTEGER,
0,
'User A sees 0 projects in other org'
);
SELECT throws_ok(
$$INSERT INTO projects (org_id, name) VALUES ('org-2-uuid', 'Hack')$$,
'new row violates row-level security policy',
'User A cannot insert into other org'
);
SELECT * FROM finish();
ROLLBACK;
Exécutez ceux-ci en CI. Chaque migration jouant avec les politiques RLS devrait envoyer la suite de tests complète dans un entraînement vigoureux.
Liste de Vérification de Déploiement en Production
Prêt à lancer ? Armez-vous de ceci :
- RLS activé sur chaque table contenant des données de locataire
- Clé de rôle de service horodatée côté serveur, nulle part près d'un client
-
org_idconvenablement indexé sur toutes les tables étendues au locataire - Fonctions d'aide d'appartenance décorées de
SECURITY DEFINERetSTABLE - Réclamations JWT personnalisées verrouillées et chargées (si sur la route JWT)
- La mise en commun des connexions est-elle accrochée au déploiement dans le cloud ?
- Les politiques RLS fraîchement sorties du QA avec pgTAP ou équivalent
-
EXPLAIN ANALYZEmonté sur les requêtes cruciales avec RLS en cours d'exécution - Le flux d'invitation/inscription ne manque-t-il pas d'amorçages d'appartenance ?
- Avez-vous activé la limitation de débit sur les points de terminaison auth ? Supabase offre des options intégrées
- Avez-vous activé RLS pour les tables du schéma
authdans le Tableau de bord Supabase (souvent une mine terrestre) - Surveillance intégrée pour toute requête lente (Tableau de bord Supabase > Base de données > Performance des Requêtes)
Lancer un produit multi-tenant et chercher quelqu'un qui a pataugé dans ces eaux auparavant ? Nos solutions de développement de CMS headless ou une rapide discussion via notre page de contact pourraient être exactement ce qu'il vous faut.
FAQ
Puis-je utiliser Supabase RLS pour les applications avec des milliers de locataires ?
Absolument. Nous avons piloté RLS de table partagée avec 5 000+ locataires et des millions de lignes sans transpirer. La sauce secrète ? L'indexation appropriée sur les colonnes org_id et les fonctions d'aide STABLE. Envisageant 50 000+ locataires ou des extravagances de milliards de lignes ? Plongez dans le partitionnement des tables par org_id ou flirtez avec une configuration de schéma par locataire.
Comment gérer la commutation de locataire quand un utilisateur appartient à plusieurs organisations ?
Gardez l'organisation active dans un cookie ou une URL (sous-domaine). Vous changez d'org ? Modifiez le sous-domaine/cookie et récupérez à nouveau. Ne glissez pas l'org active dans le JWT—cela demande une reconnexion pour changer. Un cookie que votre middleware peut consulter est la voie à suivre.
Que se passe-t-il si j'oublie d'activer RLS sur une table ?
Chaque utilisateur authentifié pourrait accéder à chaque ligne. C'est la position par défaut de PostgreSQL—aucune retenue au niveau des lignes sur les tables sans RLS. Le Tableau de bord Supabase signale les tables manquant RLS, mais intégrer ceci en CI avec des requêtes vers pg_tables et pg_policies aide aussi.
Dois-je utiliser la clé de rôle de service de Supabase ou concocter un rôle PostgreSQL personnalisé pour les tâches d'admin ?
Mostly, la clé de rôle de service suffit. Elle contourne RLS entièrement, donc c'est votre top secret pour l'utilisation côté serveur uniquement. Besoin de gouvernance granulaire (comme un rôle « admin » lurking dans toutes les orgs mais timide des suppressions) ? C'est du territoire PostgreSQL personnalisé—avancé et généralement hors de votre radar jusqu'à ce que les outils internes complexes l'exigent.
Comment exécuter des migrations de base de données sans trébucher sur les politiques RLS ?
La CLI de Supabase (supabase db push ou supabase migration) aux côtés de l'URL directe de la base de données (contournant la mise en commun) a votre dos. Glissez les éditions de politique RLS dans la même migration que les tweaks de schéma. Testez les migrations de casting contre un projet de staging—Supabase vous permet de faire tourner des branches d'aperçu sur Pro pour exactement cette raison.
Les politiques RLS peuvent-elles atteindre les données d'autres API ou services ?
Non. Les politiques RLS s'assoient confortablement en SQL, évaluées par PostgreSQL. Vous rêvez de vérifier les données externes (comme un service de drapeau de fonctionnalité) ? Cimentez ces données dans une table de base de données, puis référencez dans votre politique. Un motif typique consiste à synchroniser les statuts d'abonnement de Stripe vers une colonne organizations.plan.
Quelle est la taxe de performance de RLS par rapport au filtrage au niveau de l'application ?
Là dans nos repères Supabase Pro (2 vCPU, 8 Go RAM), RLS enduit une surcharge supplémentaire de 1-3 ms par requête pour les politiques de vérification d'appartenance de base avec les bons index. Allez à la folie avec la complexité de la politique ou les jointures et vous pourriez ajouter 5-15 ms. La tactique de réclamations JWT (stocker org_ids dans le token) la réduit en dessous de 1 ms puisqu'il n'y a pas de danse de sous-requête. Pour les applications web typiques, cette goutte de latence est négligeable.
Comment cela fonctionne-t-il avec les abonnements Supabase Realtime ?
Supabase Realtime joue par le livre des règles RLS. Syntonnisez les modifications de table et attrapez uniquement les événements de lignes auxquelles vous êtes éligible pour voir selon RLS. Cela lâche de la boîte avec zéro bricolage supplémentaire. Assurez-vous simplement que votre Supabase côté client a la session utilisateur, que @supabase/ssr gère sans accroc.