Apres avoir construit 5 projets en production avec Next.js + Supabase — dont un SaaS de 480K lignes et un site vitrine avec formulaires et analytics — je suis convaincu que ce stack est le meilleur choix pour un freelance qui veut livrer vite sans sacrifier la qualite.
Cet article est le guide pratique que j'aurais voulu avoir en commençant. Pas de theorie — des patterns testes en production, avec les pieges que j'ai rencontres et comment les eviter.
Pourquoi ce stack ?
En freelance, le temps c'est de l'argent. Literalement. Chaque heure passee a configurer de l'infra, c'est une heure non facturee. Next.js + Supabase + Vercel, c'est :
- Zero config serveur — Vercel gere le deploiement, Supabase gere la base
- Auth en 15 minutes — JWT, sessions, cookies, tout est integre
- Realtime gratuit — subscriptions Postgres sans WebSocket a configurer
- Type safety bout en bout — du schema Postgres aux composants React
- Free tier genereux — tu ne paies que quand le projet genere du revenu
Le piege numero 1 : les clients Supabase
C'est le truc qui fait perdre des heures a tout le monde. Supabase avec Next.js, c'est pas un client — c'est trois clients differents, et utiliser le mauvais casse tout silencieusement.
Client Service (API Routes uniquement) :
import { createServiceClient } from '@/lib/supabase/server'
const supabase = createServiceClient()
// Utilise service_role → bypass RLSClient Serveur (Server Components) :
import { createClient } from '@/lib/supabase/server'
const supabase = await createClient()
// Utilise anon + cookies → respecte RLSClient Navigateur (Client Components) :
import { createClient } from '@/lib/supabase/client'
const supabase = createClient()
// Utilise anon → respecte RLSL'arbre de decision est simple :
- Tu es dans une API Route ? →
createServiceClient() - Tu es dans un Server Component ? →
createClient()depuisserver.ts - Tu es dans un Client Component ? →
createClient()depuisclient.ts
Le piege classique : tu crees un formulaire de contact public. L'utilisateur n'est pas authentifie. Tu utilises le client anon dans ton API Route. Boom : "new row violates row-level security policy". Parce que l'anon key respecte RLS, et ta policy n'autorise pas l'insert anonyme — ou pire, tu n'as pas de policy du tout.
La solution : utilise createServiceClient() dans les API Routes publiques. Il bypass RLS avec la service_role key. Et ne jamais exposer cette cle cote client.
RLS : la securite qui mord
Row Level Security, c'est genial. Mais ca mord les debutants. Voici les patterns qui fonctionnent :
Pour les formulaires publics (contact, newsletter) :
-- Autoriser l'insertion anonyme
CREATE POLICY "allow_anon_insert" ON contacts
FOR INSERT TO anon WITH CHECK (true);
-- Pas de SELECT pour anon = personne ne peut lirePour les donnees utilisateur :
-- Chaque user voit uniquement ses donnees
CREATE POLICY "user_own_data" ON todos
FOR ALL TO authenticated
USING (auth.uid() = user_id);Regle d'or : RLS est active par defaut sur toutes tes tables. Si tu oublies de creer une policy, rien ne passe. C'est securise par defaut, mais ca peut etre frustrant quand tu debogues un formulaire qui "ne marche pas" alors que le code est correct.
Le pattern API Route qui marche
Apres plusieurs iterations, voici le pattern que j'utilise dans tous mes projets :
- Rate limiting — 5 requetes / 15 minutes par IP (en memoire, suffisant pour un MVP)
- Validation Zod — un schema par endpoint, parse avant tout
- Insert Supabase — avec
createServiceClient() - Analytics — tracking PostHog server-side
- Reponse JSON — toujours un
{ success: true/false }
Ce pipeline en 5 etapes est replique sur chaque endpoint. C'est repetitif ? Oui. Mais c'est aussi previsible, testable, et debuggable. En freelance, la previsibilite vaut plus que l'elegance.
Type safety de bout en bout
Un des plus gros avantages du combo Next.js + Supabase : le typage ne s'arrete pas a React.
Le workflow :
- Tu modifies le schema dans une migration SQL
- Tu lances
npm run supabase:types - TypeScript te dit immediatement tous les endroits du code affectes
Plus de "j'ai rename une colonne et je le decouvre en production". Le compilateur attrape tout. Combine avec des schemas Zod pour la validation runtime, tu as une double couche de securite sur les types.
Deploiement : le workflow en 3 commandes
C'est la que le stack brille vraiment. Deployer en production :
supabase db push— applique les migrations sur la base distantegit push— Vercel detecte, build, deploie automatiquementnpm run test:e2e:prod— smoke tests contre la prod
Pas de Docker a configurer. Pas de serveur a maintenir. Pas de CI/CD complexe a ecrire. Tu push, c'est en prod. Le free tier Vercel + le free tier Supabase couvrent la majorite des projets freelance sans sortir la carte bleue.
Les gotchas en production
Quelques pieges que j'ai rencontres sur mes projets en production :
- PostHog region mismatch — cle API EU pointant vers l'endpoint US. Resultat : 404 sur
config.js, 401 sur les feature flags. Solution : verifier queNEXT_PUBLIC_POSTHOG_HOSTcorrespond a la region de ta cle. - Rate limiting en memoire — ca marche bien pour un MVP, mais ca se reinitialise a chaque redeploy Vercel (serverless = stateless). Pour la production serieuse, passe a Upstash Redis.
- Variables d'environnement — seules les variables prefixees
NEXT_PUBLIC_sont exposees au navigateur. Oublie le prefixe sur ta Supabase URL = formulaire casse sans erreur visible cote serveur. - Server Components par defaut — Next.js 15 rend tout en Server Component. Ajoute
'use client'uniquement si tu as besoin de hooks, d'event handlers ou d'etat. Moins de client JS = meilleures perfs = meilleur Core Web Vitals.
Mon conseil pour les freelances
Si tu debutes en freelance et que tu cherches un stack, arrete de comparer. Prends Next.js + Supabase + Vercel, livre ton premier projet, et iterate. Le meilleur stack, c'est celui avec lequel tu livres — pas celui qui a le plus d'etoiles sur GitHub.
Les patterns decrits dans cet article — trois clients Supabase, RLS par defaut, validation Zod, API Routes en pipeline, types auto-generes — couvrent 90% des besoins freelance. Le reste, tu l'apprendras en livrant.
Et quand tu tomberas sur le message "new row violates row-level security policy", tu sauras exactement quoi faire.