Bootstrapped & Built: a Lean SaaS Tech Stack

Bootstrapped & Built: a Lean SaaS Tech Stack

Développement Web

LE CONSTAT

Quand on est bootstrappé, on n’a pas d’équipe d’ingénieurs infrastructure. Pas de crédit AWS à 50 000 dollars par mois. On a un ordinateur portable, une carte de crédit avec un plafond modeste, et une envie viscérale de livrer avant que la trésorerie ne s’épuise.

J’y suis passé. Deux fois.

La première fois, j’ai tout sur-ingénié. Kubernetes. Microservices. Une file de messages pour un produit sans aucun utilisateur. J’ai brûlé trois mois et la quasi-totalité de mes économies avant même d’avoir un MVP fonctionnel.

La deuxième fois, je me suis mis au lean. J’ai livré en trois semaines. J’avais des clients payants en six. Et je n’ai jamais regardé en arrière.

Voici la stack qui m’y a conduit – et pourquoi je la choisirais à nouveau.

CE QUE « LEAN » VEUT VRAIMENT DIRE

Mettons les choses au clair : lean ≠ bon marché.

Lean signifie que chaque outil de la stack mérite sa place grâce à l’un de ces trois critères :

· Rapidité – Est-ce que ça m’aide à livrer plus vite, maintenant ?

· Coût – Est-ce que ça maintient ma dépense mensuelle à un niveau raisonnable ?

· Simplicité – Est-ce que ça réduit la charge cognitive pour me laisser me concentrer sur l’essentiel (les clients, pas les fichiers de config) ?

Si un outil ne coche pas au moins deux de ces cases, il dégage.

Autre point important : lean ≠ permanent. Votre stack à 0 utilisateur ne devrait pas ressembler à votre stack à 10 000 utilisateurs. L’objectif, c’est de survivre assez longtemps pour avoir ce problème de « mise à l’échelle » en premier lieu.

LA STACK EN UN COUP D’ŒIL

Avant d’entrer dans le détail, voici une vue d’ensemble de ce que nous utilisons :

· Frontend : next.js 14 (App Router)

· Hébergement : vercel (Hobby/Pro)

· Backend : routes API next.js + fonctions Serverless

· Base de données : supabase (PostgreSQL)

· ORM : prisma

· Auth : supabase Auth + NextAuth.js

· Paiements : stripe

· E-mail : resend

· Tâches en arrière-plan : vercel Cron Jobs + Upstash Redis

· Analytics : plausible

· Monitoring : sentry (erreurs) + Vercel Analytics

· Stockage de fichiers : supabase Storage

Coût mensuel à 0 utilisateur : ~20-30 $

Coût mensuel à 100 utilisateurs : ~50-80 $

Coût mensuel à 1 000 utilisateurs : ~150-250 $

Oui, vraiment.

FRONTEND : NEXT.JS 14 AVEC L’APP ROUTER

Pourquoi je l’ai choisi : next.js est le couteau suisse du développement web moderne. Composants serveur, composants client, routes API, génération statique, fonctions serverless – il fait tout dans un package cohérent.

L’argument lean :

· Rapidité : un seul framework gère le frontend, le backend et le routage. Pas de changement de contexte entre les outils.

· Coût : le niveau gratuit de vercel est généreux. Le plan Hobby (20 $/mois) couvre la plupart des besoins en phase de démarrage.

· Simplicité : l’App Router, malgré sa courbe d’apprentissage, impose de bonnes pratiques. La mise en cache est intégrée. L’optimisation des images est automatique.

La contrepartie : un couplage fournisseur avec Vercel. Mais honnêtement ? Pour un fondateur bootstrappé, c’est une fonctionnalité, pas un défaut. Je veux penser à mon produit, pas configurer des distributions CloudFront.

Ce que j’ai écarté : react Native (trop tôt), le CRA classique (trop lent), Remix (excellent mais moins d’intégrations écosystème).

BASE DE DONNÉES : SUPABASE (POSTGRESQL)

Pourquoi je l’ai choisi : postgres est la Toyota Corolla des bases de données – fiable, bien connue, et qui fonctionne tout simplement. Supabase y ajoute une belle interface, des abonnements en temps réel et une auth intégrée.

L’argument lean :

· Rapidité : lancer une instance Postgres prête pour la production prend 30 secondes. Le tableau de bord est suffisamment intuitif pour que je touche rarement à la CLI.

· Coût : le niveau gratuit offre 500 Mo de base de données, 2 Go de bande passante et 50 000 utilisateurs actifs mensuels pour l’auth. C’est largement suffisant pour vos 500 premiers clients payants.

· Simplicité : pas de Redis séparé pour le cache (pour l’instant), pas de réplication complexe. Juste du connection pooling, et c’est parti.

La contrepartie : supabase n’est pas aussi mature qu’AWS RDS pour les charges de travail d’entreprise. Mais je ne suis pas une entreprise. Je suis un type dans un café qui essaie d’atteindre 5 000 $ de MRR.

Ce que j’ai écarté : MongoDB (pas de SQL ? non merci, j’aime les jointures), Firebase (un couplage fournisseur qui fait mal), Postgres brut avec pgAdmin (la vie est trop courte).

ORM : PRISMA

Pourquoi je l’ai choisi : j’écrivais du SQL brut. Puis j’ai utilisé Knex. Puis TypeORM. Puis j’ai découvert Prisma et je n’ai jamais regardé en arrière.

L’argument lean :

· Rapidité : des requêtes de base de données typées avec autocomplétion. Je détecte les erreurs à la compilation, pas à l’exécution. Ça seul m’économise des heures de débogage.

· Simplicité : un seul fichier schema.prisma qui définit l’intégralité de mon modèle de données. Les migrations sont générées, pas écrites à la main.

Un exemple rapide :

model User {

id String @id @default(cuid())

email String @unique

name String ?

createdAt DateTime @default(now())

updatedAt DateTime @updatedAt

subscriptions Subscription[]

}

model Subscription {

id String @id @default(cuid())

userId String

user User @relation(fields : [userId], references : [id])

stripeId String @unique

status String // active, canceled, past_due

createdAt DateTime @default(now())

}

C’est tout. Lancez prisma migrate dev, et vous obtenez un schéma prêt pour la production avec les migrations suivies dans Git.

La contrepartie : le moteur de requêtes de prisma ajoute une légère couche de surcharge. Pour 99 % des applications bootstrappées, c’est imperceptible. Si vous faites des pipelines de données massifs, peut-être à reconsidérer. Mais vous ne le faites pas. Vous construisez un SaaS.

Ce que j’ai écarté : drizzle (prometteur mais plus récent, moins de ressources), TypeORM (n’avons-nous pas assez souffert ?), les chaînes SQL brutes (surtout pas).

AUTHENTIFICATION : SUPABASE AUTH + NEXTAUTH.JS

Pourquoi je l’ai choisi : l’auth est la seule chose que vous ne devriez absolument pas construire vous-même. Hachage des mots de passe ? Gestion des sessions ? Refresh tokens ? Flux OAuth ? Juste… non.

L’argument lean :

· Rapidité : supabase gère l’e-mail/mot de passe, les magic links, Google, GitHub – le tout avec quelques lignes de configuration.

· Coût : inclus dans le niveau gratuit de Supabase. 0 $ supplémentaire jusqu’à la mise à l’échelle.

· Simplicité : NextAuth.js (désormais Auth.js) s’intègre parfaitement avec Next.js et Supabase comme fournisseur.

L’extrait de configuration :

//app/api/auth/[…nextauth]/route.ts

import NextAuth from “next-auth” ;

import GoogleProvider from “next-auth/providers/google” ;

import { SupabaseAdapter } from “@auth/supabase-adapter” ;

export const authOptions = {

providers : [

GoogleProvider({

clientId : process.env.GOOGLE_CLIENT_ID !,

clientSecret : process.env.GOOGLE_CLIENT_SECRET !,

}),

],

adapter : SupabaseAdapter({

url : process.env.NEXT_PUBLIC_SUPABASE_URL !,

secret : process.env.SUPABASE_SERVICE_ROLE_KEY !,

}),

} ;

const handler = NextAuth(authOptions) ;

export { handler as GET, handler as POST } ;

C’est tout. Les mots de passe sont hachés. Les sessions sont gérées. Les flux OAuth fonctionnent. On passe à la suite.

La contrepartie : vous dépendez de Supabase pour l’auth. S’ils tombent, vous tombez. Mais ils affichent 99,99 % de disponibilité selon mon expérience, et je préfère prendre ce risque plutôt qu’écrire ma propre auth.

Ce que j’ai écarté : clerk (excellent mais coûteux dès le début), Auth0 (cher à l’échelle), les flux JWT personnalisés + refresh (vous êtes fou ?).

PAIEMENTS : STRIPE

Pourquoi je l’ai choisi : stripe est la référence incontournable. Ses API sont un plaisir à utiliser, sa documentation est exceptionnelle, et il gère l’intégralité du cycle de paiement, du checkout à la gestion des abonnements en passant par les relances de paiements échoués.

L’argument lean :

· Rapidité : stripe Checkout est une page hébergée vers laquelle vous redirigez les utilisateurs. Pas de maux de tête liés à la conformité PCI, pas de formulaires de paiement à construire de zéro.

· Coût : 2,9 % + 0,30 $ par transaction. Ce n’est pas donné, mais c’est la norme. Et vous pouvez répercuter les frais sur les clients si vous le souhaitez.

· Simplicité : les webhooks sont simples à configurer, et le tableau de bord vous donne une visibilité complète sur le chiffre d’affaires, le churn et les paiements échoués.

Les points d’intégration clés :

1. Créer une session Checkout – Rediriger l’utilisateur vers la page hébergée de Stripe.

2. Écouteur de webhook – Gérer checkout.session.completed, invoice.payment_failed, etc.

3. Mettre à jour le statut d’abonnement de l’utilisateur – Synchroniser les données Stripe avec votre base de données Supabase.

La contrepartie : les frais de stripe s’accumulent. À grande échelle (disons plus de 100 000 $ de MRR), vous pourriez envisager de passer à Paddle ou même à une solution maison. Mais à ce stade, vous aurez les ressources pour prendre cette décision.

Ce que j’ai écarté : paddle (mieux pour la TVA européenne mais plus complexe), Braintree (correct mais Stripe est meilleur), PayPal (surtout pas).

E-MAIL : RESEND

Pourquoi je l’ai choisi : les e-mails transactionnels sont essentiels – e-mails de bienvenue, réinitialisations de mot de passe, reçus de facture, etc. Resend est en quelque sorte le « Vercel de l’e-mail ».

L’argument lean :

· Rapidité : un appel API, une ligne de code, et votre e-mail est envoyé. Pas de config SMTP, pas de cauchemars de délivrabilité.

· Coût : 3 000 e-mails/mois gratuits. Largement suffisant en phase de démarrage.

· Simplicité : react Email vous permet de construire des templates d’e-mail avec des composants, et non du HTML à base de tableaux sorti des années 2000.

Un exemple d’envoi :

import { Resend } from ‘resend’ ;

const resend = new Resend(process.env.RESEND_API_KEY) ;

await resend.emails.send({

from : ‘onboarding@yourapp.com’,

to : user.email,

subject : ‘Welcome to [App Name] !’,

html : `<p>Thanks for signing up, ${user.name} !</p>`,

}) ;

La contrepartie : resend est plus récent que SendGrid ou Mailgun. Ils ont eu quelques pannes mineures. Mais leur expérience développeur est tellement supérieure que je leur accorde le bénéfice du doute.

Ce que j’ai écarté : SendGrid (API maladroite, tableau de bord exécrable), Mailgun (meilleur mais toujours vieillot), AWS SES (trop de configuration, trop peu d’expérience développeur).

TÂCHES EN ARRIÈRE-PLAN : VERCEL CRON JOBS + UPSTASH REDIS

Pourquoi je l’ai choisi : tout SaaS a tôt ou tard besoin de tâches en arrière-plan – envoi d’e-mails récapitulatifs, nettoyage des anciennes données, synchronisation avec des API externes, etc.

L’argument lean :

· Rapidité : les vercel Cron Jobs sont des fonctions serverless qui s’exécutent selon un planning. Pas d’infrastructure de workers séparée.

· Coût : le niveau gratuit de vercel inclut les cron jobs. Upstash Redis dispose d’un niveau gratuit généreux pour la gestion de files.

· Simplicité : vous écrivez une fonction, vous la déployez, elle s’exécute. C’est tout.

Le schéma que j’utilise :

1. Le cron job se déclenche toutes les heures (ou tous les jours).

2. Il vérifie les tâches en attente dans la base de données.

3. Pour chaque tâche, il pousse un job dans la file Upstash Redis.

4. Une autre fonction serverless traite les jobs depuis la file.

La contrepartie : vercel impose une limite d’exécution de 10 secondes sur le niveau gratuit. Pour les jobs plus longs, il faut passer à un plan supérieur ou utiliser une autre solution. Mais pour la plupart des jobs en phase de démarrage (envoi d’e-mails, mise à jour d’enregistrements), 10 secondes suffisent largement.

Ce que j’ai écarté : BullMQ (excellent mais plus lourd), RabbitMQ (trop d’infrastructure), AWS SQS + Lambda (plus complexe, plus cher).

ANALYTICS : PLAUSIBLE

Pourquoi je l’ai choisi : j’ai besoin de savoir d’où viennent mes utilisateurs et ce qu’ils font. Je n’ai pas besoin de traquer chaque micro-interaction.

L’argument lean :

· Coût : 9 $/mois jusqu’à 10 000 pages vues. C’est moins que mon budget café.

· Confidentialité : pas de cookies, pas de casse-tête RGPD, pas de tracking intrusif.

· Simplicité : ajoutez une balise script dans votre layout, c’est réglé.

La contrepartie : vous n’avez pas d’enregistrements de session ni de heatmaps. Si vous en avez besoin, envisagez Hotjar ou PostHog. Mais en phase précoce, je trouve que les métriques de haut niveau de Plausible (sources de trafic, taux de rebond, pages les plus vues) sont parfaitement suffisantes.

Ce que j’ai écarté : Google Analytics (lourd, intrusif, déroutant), Mixpanel (cher, surdimensionné), Amplitude (pareil).

MONITORING : SENTRY + VERCEL ANALYTICS

Pourquoi je l’ai choisi : les choses vont planter. Vous devez savoir quand et pourquoi.

L’argument lean :

· Sentry – Le niveau gratuit capture les erreurs et vous fournit des stack traces. Vous dormirez mieux.

· Vercel Analytics – Intégré à Vercel, il suit les Core Web Vitals et les performances des pages.

La contrepartie : le niveau gratuit de sentry est généreux pour la phase de démarrage. Si vous traitez des millions d’événements, vous devrez passer à un plan supérieur – mais à ce stade, vous aurez du chiffre d’affaires.

Ce que j’ai écarté : New Relic (cher), Datadog (encore plus cher), la journalisation d’erreurs maison (surtout pas).

STOCKAGE DE FICHIERS : SUPABASE STORAGE

Pourquoi je l’ai choisi : si vos utilisateurs téléchargent des fichiers (avatars, documents, images), il vous faut un endroit pour les stocker.

L’argument lean :

· Rapidité : supabase Storage s’intègre avec votre auth existante. Pas de clés API séparées ni de buckets à gérer.

· Coût : le niveau gratuit inclut 1 Go de stockage, largement suffisant en phase de démarrage.

· Simplicité : uploadez, servez et supprimez des fichiers en quelques lignes de code.

const { data, error } = await supabase.storage

.from(‘avatars’)

.upload(`user-${userId}/avatar.jpg`, file) ;

La contrepartie : à grande échelle, vous voudrez passer à S3 ou Cloudflare R2 pour des raisons de coût. Mais pour vos 1 000 premiers utilisateurs, Supabase Storage convient parfaitement.

Ce que j’ai écarté : s3 (configuration plus complexe), Cloudinary (cher), Firebase Storage (couplage fournisseur).

LE DÉTAIL COMPLET DES COÛTS

Soyons transparents sur l’argent. Voici ce que coûte chaque service :

· Vercel : 0 $ sur le plan Hobby, 20 $/mois pour le Pro

· Supabase : 0 $ sur le niveau gratuit, 25 $/mois pour le Pro

· Stripe : 0 $ de base, 2,9 % + 0,30 $ par transaction

· Resend : 0 $ pour les 3 000 premiers e-mails, 20 $/mois pour 50 000

· Upstash Redis : 0 $ sur le niveau gratuit, 15 $/mois en payant

· Plausible : 9 $/mois (pas de niveau gratuit)

· Sentry : 0 $ sur le niveau gratuit, 29 $/mois en payant

Total à 0 utilisateur : ~20-30 $ par mois

Total à 1 000 clients payants : ~120-200 $ par mois

À 1 000 clients payants, cela représente environ 5 à 10 % de votre chiffre d’affaires en coûts d’infrastructure – ce qui est extrêmement sain.

CE QUE J’AURAIS FAIT DIFFÉREMMENT

1. Commencer avec Supabase, pas Firebase.

Ma première tentative utilisait Firebase. La migration hors de cet écosystème a été un cauchemar. Supabase (Postgres) est bien plus facile à migrer si nécessaire.

2. Livrer le MVP plus vite.

J’ai passé trop de temps à peaufiner l’interface avant de valider le problème. Next.js + shadcn/ui me permettent désormais de livrer un MVP soigné en jours, pas en semaines.

3. Créer une page de liste d’attente en premier.

Avant d’écrire la moindre ligne de code, j’aurais dû construire une landing page avec Resend + Supabase pour collecter des e-mails. Validez la demande avant de construire.

4. Utiliser les feature flags plus tôt.

LaunchDarkly est surdimensionné, mais une simple table de flags dans Supabase me permet de déployer des fonctionnalités à 10 % des utilisateurs et de tester avant la sortie complète.

LA PHILOSOPHIE LEAN EN PRATIQUE

Voici ce que j’ai appris en construisant avec cette stack :

« You aren’t going to need it » (YAGNI) est votre meilleur ami.

N’ajoutez pas de cache tant que votre base de données n’est pas lente. N’ajoutez pas de file de messages tant que vous n’avez pas de jobs en arrière-plan. N’ajoutez pas un moteur de recherche tant que les utilisateurs ne se plaignent pas de la recherche.

Déléguez les tâches rébarbatives.

Auth, paiements, e-mail – laissez des tiers s’en occuper. Votre rôle est de construire la proposition de valeur unique, pas de réinventer l’authentification.

Livrez maintenant, optimisez ensuite.

Vos premiers utilisateurs se moquent de votre stack. Ce qui les intéresse, c’est de savoir si votre produit résout leur problème. Optimisez pour leur temps, pas pour votre architecture.

Connaissez vos portes de sortie.

Couplage avec Vercel ? Vous pouvez héberger Next.js sur n’importe quoi. Couplage avec Supabase ? C’est juste du Postgres. Couplage avec Stripe ? Il existe des alternatives. Connaissez vos options, mais ne changez pas prématurément.

RÉFLEXIONS FINALES

Votre tech stack est un moyen, pas une fin en soi.

La meilleure stack est celle qui vous permet de livrer rapidement, d’itérer sur la base des retours, et de maintenir les coûts bas jusqu’à ce que vous ayez validé l’adéquation produit-marché.

La stack que j’ai partagée ici m’a amené à des clients payants en moins d’un mois, sans aucun casse-tête d’infrastructure, pour moins cher qu’un bon dîner au restaurant.

Est-ce qu’elle fonctionnera pour vous ? Peut-être pas à l’identique. Mais la philosophie, oui :

· Commencez simple.

· N’ajoutez de la complexité que lorsqu’on vous y force.

· Utilisez des services de référence pour les fonctions non essentielles.

· Ne laissez jamais votre stack devenir le produit.

Maintenant, allez construire quelque chose pour lequel les gens paieront.