Comment Lancer un SaaS en 2026 : Le Guide Complet avec React + Symfony

Meta description: Découvrez comment créer un SaaS de zéro en 2026 avec React et Symfony. Architecture, auth, paiements Stripe, déploiement Docker — guide complet + boilerplate prêt à l'emploi.

Introduction : Pourquoi la plupart des devs perdent 3 mois avant d'avoir une première version

Vous avez une idée de SaaS. Vous savez coder. Mais avant d'écrire la première ligne de logique métier, il faut mettre en place :

C'est entre 3 et 6 semaines de travail rien que pour l'infrastructure. Et encore, si vous ne faites pas d'erreurs d'architecture qui vous rattrapent 6 mois plus tard.

Ce guide vous explique comment construire cette infrastructure correctement dès le départ — et comment ne plus jamais perdre ce temps.


Partie 1 : L'architecture SaaS en 2026

Frontend vs Backend : pourquoi séparer ?

En 2026, la séparation frontend/backend n'est plus optionnelle pour un SaaS sérieux :

Frontend React (TypeScript) Backend API (Symfony 7) Avantage concret : Votre frontend React peut être utilisé par une app mobile React Native sans toucher au backend. Vos équipes peuvent travailler en parallèle.

La structure de base

saas-project/

├── frontend/ # React 18 + TypeScript + Vite

│ ├── src/

│ │ ├── components/

│ │ ├── pages/

│ │ ├── hooks/

│ │ ├── contexts/ # Auth, Theme, etc.

│ │ └── lib/ # API client, utils

├── backend/ # Symfony 7 + API Platform

│ ├── src/

│ │ ├── Entity/

│ │ ├── Controller/

│ │ ├── Service/

│ │ └── Security/

├── nginx/ # Config reverse proxy

└── docker-compose.yml


Partie 2 : L'authentification — le composant le plus critique

JWT vs Sessions : que choisir ?

Pour un SaaS moderne avec une SPA React, JWT est le standard :

// AuthContext.tsx — le cœur de votre auth

interface AuthContextType {

user: User | null;

login: (credentials: LoginCredentials) => Promise<void>;

logout: () => void;

isLoading: boolean;

}

export const AuthContext = createContext<AuthContextType | null>(null);

export const AuthProvider = ({ children }: { children: ReactNode }) => {

const [user, setUser] = useState<User | null>(null);

const [isLoading, setIsLoading] = useState(true);

useEffect(() => {

// Vérifie le token au démarrage

const token = localStorage.getItem('access_token');

if (token) {

validateToken(token).then(setUser).finally(() => setIsLoading(false));

} else {

setIsLoading(false);

}

}, []);

// ...

};

Ce qu'il faut absolument implémenter :
  • Access token (15 min) + Refresh token (7 jours)
  • Blacklist des tokens révoqués (Redis)
  • Rate limiting sur /login
  • Route protégée côté React ()
  • Côté Symfony

    // src/Security/JwtAuthenticator.php
    

    class JwtAuthenticator extends AbstractAuthenticator

    {

    public function authenticate(Request $request): Passport

    {

    $token = $this->extractToken($request);

    try {

    $payload = $this->jwtService->decode($token);

    } catch (JwtException $e) {

    throw new AuthenticationException('Invalid token');

    }

    return new SelfValidatingPassport(

    new UserBadge($payload['email'])

    );

    }

    }


    Partie 3 : Les paiements Stripe

    Setup en 4 étapes

    Étape 1 — Créer les produits dans Stripe Dashboard Étape 2 — Checkout Session
    // src/Controller/PaymentController.php
    

    #[Route('/create-checkout', methods: ['POST'])]

    public function createCheckout(Request $request): JsonResponse

    {

    $session = $this->stripe->checkout->sessions->create([

    'payment_method_types' => ['card'],

    'line_items' => [[

    'price' => $request->get('price_id'),

    'quantity' => 1,

    ]],

    'mode' => 'subscription',

    'success_url' => $this->getParameter('app_url') . '/success?session_id={CHECKOUT_SESSION_ID}',

    'cancel_url' => $this->getParameter('app_url') . '/pricing',

    'metadata' => [

    'user_id' => $this->getUser()->getId(),

    ],

    ]);

    return $this->json(['url' => $session->url]);

    }

    Étape 3 — Webhook pour confirmer le paiement
    #[Route('/webhook/stripe', methods: ['POST'])]
    

    public function stripeWebhook(Request $request): Response

    {

    $event = Webhook::constructEvent(

    $request->getContent(),

    $request->headers->get('Stripe-Signature'),

    $this->stripeWebhookSecret

    );

    switch ($event->type) {

    case 'checkout.session.completed':

    $this->handleCheckoutCompleted($event->data->object);

    break;

    case 'customer.subscription.deleted':

    $this->handleSubscriptionCancelled($event->data->object);

    break;

    }

    return new Response('OK', 200);

    }

    Étape 4 — Portail client Stripe

    Pas besoin de développer une page de gestion abonnement — Stripe le fait pour vous :

    $session = $this->stripe->billingPortal->sessions->create([
    

    'customer' => $user->getStripeCustomerId(),

    'return_url' => $this->generateUrl('dashboard'),

    ]);


    Partie 4 : Le déploiement Docker

    docker-compose.yml de production

    version: '3.8'
    

    services:

    frontend:

    build:

    context: ./frontend

    target: production

    restart: unless-stopped

    backend:

    build: ./backend

    environment:

    DATABASE_URL: postgresql://user:pass@db:5432/saas

    JWT_SECRET: ${JWT_SECRET}

    STRIPE_KEY: ${STRIPE_SECRET_KEY}

    restart: unless-stopped

    nginx:

    image: nginx:alpine

    ports:

    - "80:80"

    - "443:443"

    volumes:

    - ./nginx/nginx.conf:/etc/nginx/nginx.conf

    - ./ssl:/etc/ssl/certs

    depends_on:

    - frontend

    - backend

    db:

    image: postgres:16-alpine

    volumes:

    - postgres_data:/var/lib/postgresql/data

    restart: unless-stopped

    volumes:

    postgres_data:

    GitHub Actions CI/CD

    # .github/workflows/deploy.yml
    

    name: Deploy to Production

    on:

    push:

    branches: [main]

    jobs:

    test:

    runs-on: ubuntu-latest

    steps:

    - uses: actions/checkout@v4

    - name: Run frontend tests

    run: cd frontend && npm ci && npm test

    - name: Run backend tests

    run: cd backend && composer install && php bin/phpunit

    deploy:

    needs: test

    runs-on: ubuntu-latest

    steps:

    - name: Deploy via SSH

    uses: appleboy/ssh-action@v1

    with:

    host: ${{ secrets.SERVER_HOST }}

    username: deploy

    key: ${{ secrets.SSH_KEY }}

    script: |

    cd /var/www/saas

    git pull origin main

    docker-compose up -d --build


    Partie 5 : Les erreurs courantes à éviter

    1. Ne pas séparer les environnements

    Avoir un .env.local pour le dev et un .env.production pour la prod — c'est la base. Ne jamais hardcoder des clés API.

    2. Oublier le rate limiting

    Un endpoint /login sans rate limiting = invitation aux attaques brute force. Symfony a symfony/rate-limiter.

    3. Ne pas penser à la scalabilité dès le départ

    4. Déployer sans monitoring

    Vous ne saurez pas que votre app est down avant que les clients vous le disent. Utilisez au minimum :


    Partie 6 : Combien de temps ça prend vraiment ?

    | Composant | De zéro | Avec un boilerplate |

    |-----------|---------|---------------------|

    | Auth (JWT + refresh + forgot password) | 3-5 jours | 0 (déjà fait) |

    | Paiements Stripe | 2-3 jours | 0 (déjà fait) |

    | Dashboard admin | 3-4 jours | 0 (déjà fait) |

    | Docker + CI/CD | 1-2 jours | 0 (déjà fait) |

    | Landing page | 2-3 jours | 0 (déjà fait) |

    | Total infra | 2-4 semaines | < 1 heure |

    La vraie question : Voulez-vous passer 3 semaines à configurer de l'infrastructure, ou à construire la vraie valeur de votre SaaS ?

    Conclusion : Le bon point de départ

    Lancer un SaaS en 2026 demande une solide base technique. L'architecture React + Symfony reste un choix excellent : TypeScript sur le frontend pour la robustesse, Symfony pour la structure et la sécurité côté API.

    Le plus difficile n'est pas de savoir comment faire — c'est de trouver le temps de tout mettre en place correctement.

    Si vous voulez démarrer avec une base solide, le SaaS Boilerplate React + Symfony contient tout ce dont vous avez besoin : auth complète, Stripe, Docker, CI/CD GitHub Actions, dashboard admin — npm install && npm run dev et vous êtes prêt en moins d'une heure.

    FAQ

    Q : React + Symfony, c'est une bonne stack pour un SaaS en 2026 ?

    Oui. React reste le framework frontend dominant (65% des offres d'emploi frontend en France). Symfony est utilisé par les entreprises qui ont besoin de robustesse et de sécurité. La combinaison est battle-tested.

    Q : Est-ce que je dois connaître Symfony pour utiliser le boilerplate ?

    Oui, un niveau intermédiaire est nécessaire. Le boilerplate est pour des développeurs, pas des débutants complets.

    Q : Pourquoi pas Next.js + Laravel ?

    Les deux fonctionnent très bien. React + Symfony est plus adapté si vous avez déjà de l'expérience PHP/Symfony dans votre équipe.

    Q : Est-ce qu'il y a du multi-tenant dans le boilerplate ?

    La structure est préparée pour le multi-tenant (organisations + membres) mais l'implémentation complète est à votre charge selon votre modèle.

    Q : Quel hébergement recommandez-vous ?

    Pour démarrer : un VPS 8 Go RAM (Hetzner ou OVH, ~10-15€/mois). Docker-compose tourne sans problème. Pour scaler : DigitalOcean App Platform ou AWS ECS.

    Q : Les paiements Stripe sont en mode test ou production ?

    Le boilerplate inclut les deux configurations. Les clés Stripe test sont dans .env.local, les clés prod dans .env.production.

    Q : Quelle licence pour le code du boilerplate ?

    Licence commerciale — vous pouvez l'utiliser pour autant de projets que vous voulez, mais pas le revendre tel quel.


    Cet article fait partie de la série "Lancer un SaaS en 2026" sur NetRevision.