De Webflow à l’auto-hébergé : construire une architecture CMS économique et sécurisée

Jérémie Poutrin

Jérémie Poutrin

14 septembre 2025 • il y a 12 jours

De Webflow à l’auto-hébergé : construire une architecture CMS économique et sécurisée

Dans cet article, je raconte comment j’ai migré mon portfolio de Webflow vers une architecture CMS auto-hébergée avec Directus et Astro. Objectif : réduire mes coûts d’hébergement de 95 %, améliorer la sécurité grâce à une approche Zero Trust, et gagner en flexibilité pour gérer plusieurs projets clients sans frais supplémentaires. J’explique les choix techniques (Directus multi-tenant avec Ansible, génération statique avec Astro, stratégie de cache d’images, intégration Cloudflare/Netlify), les gains de performance (score Lighthouse 98, TTFB réduit de 87 %), ainsi que l’impact de l’IA (Claude Code) sur la rapidité de mise en œuvre. L’article se termine par les leçons apprises, les défis rencontrés et les pistes d’évolution futures.

De Webflow à l’auto-hébergé : construire une architecture CMS économique et sécurisée

Le problème : quand le SaaS devient un goulot d’étranglement

Comme beaucoup de développeurs, j’ai commencé avec Webflow pour mon site portfolio. La promesse : facilité d’usage, édition visuelle, déploiement rapide. Mais après un an, j’ai rencontré plusieurs limites :

  • Coût : 420 €/an pour un simple portfolio avec CMS
  • Limitations : accès API restreint, options de personnalisation limitées
  • Scalabilité clients : chaque projet nécessitait un plan payant distinct
  • Expérience développeur : lutter contre la plateforme plutôt que construire avec
  • Gestion de version : pas d’intégration Git correcte pour le versioning du contenu

Le déclic est venu quand j’ai réalisé que je payais des prix « entreprise » pour des fonctionnalités basiques, alors même que j’avais un home lab inutilisé. Il était temps d’architecturer mieux.

La solution : une JAMstack moderne avec CMS auto-hébergé

graph TB
    subgraph "Content Creation"
        A[Directus CMS
Self-Hosted] end subgraph "Build Pipeline" B[GitHub Repository] C[Astro SSG] D[Image Cache] end subgraph "Deployment" E[Netlify CDN] F[Static Site] end subgraph "Security Layer" G[Cloudflare Zero Trust] end A -->|Protected by| G G -->|Secure API| C B --> C C --> D D --> E E --> F style A fill:#f9f,stroke:#333,stroke-width:4px style G fill:#bbf,stroke:#333,stroke-width:2px style E fill:#bfb,stroke:#333,stroke-width:2px

Vue d’ensemble de l’architecture

La nouvelle architecture repose sur :

  • Directus CMS : CMS headless open-source, API-first
  • Astro : générateur de sites statiques ultra-rapide
  • Cloudflare Zero Trust : sécurité de niveau entreprise sans le coût
  • Netlify : CDN mondial et déploiement continu
  • Infrastructure Home Lab : matériel existant, coût nul additionnel

Mise en œuvre technique

1. Directus multi-tenant avec Ansible

Pour rendre Directus multi-tenant, j’ai modélisé un projet par tenant et relié à la fois directus_users.project_id et articles.project_id en M2O vers projects.

Côté sécurité, j’ai créé plusieurs policies “Project Member” qui filtrent tout (read/update/delete) avec { "project_id": { "_eq": "$CURRENT_USER.project_id" } }, empêchent la création hors périmètre via une validation identique, et préremplit le champ à la création grâce à un Flow “Before Create” qui tamponne payload.project_id = $accountability.user.project_id (le champ est ensuite verrouillé en édition).

J’ai appliqué la même logique aux fichiers en ajoutant project_id à directus_files, puis en préfixant filename_disk côté Flow (ex. projects//.ext) pour garder un stockage S3 (Scaleway) propre et segmenté ;

Les URLs publiques sont servies via CDN pour que Astro (sur Netlify) puisse optimiser les images au build, tandis que l’admin/API Directus restent derrière Cloudflare Zero Trust. Les admins disposent d’une politique sans filtre pour superviser l’ensemble.

Résultat : chaque utilisateur ne voit et ne crée que les contenus de son projet, les médias sont correctement isolés au niveau données et stockage, et la chaîne d’édition → build reste simple et performante.

Cette approche me permet de :

  • Créer de nouvelles instances en quelques minutes pour des clients
  • Maintenir une configuration homogène sur tous les déploiements
  • Monter en charge horizontalement sans coûts de licences supplémentaires

2. Architecture Zero Trust

graph LR
    subgraph "Internet"
        A[User Browser]
        B[Build Server]
    end

    subgraph "Cloudflare"
        C[Zero Trust Gateway]
        D[Access Policies]
        E[Service Token]
    end

    subgraph "Home Network"
        F[Reverse Proxy]
        G[Directus CMS]
        H[SQLite DB]
    end

    A -->|HTTPS| C
    B -->|Service Auth| E
    C --> D
    D -->|Validated| F
    E -->|API Access| F
    F --> G
    G --> H

    style C fill:#ff9,stroke:#333,stroke-width:2px
    style D fill:#f99,stroke:#333,stroke-width:2px

Détails de l’implémentation sécurité :

// src/lib/directus.js
import { config } from 'dotenv';
config();

const DIRECTUS_URL = process.env.DIRECTUS_URL;
const CF_ACCESS_CLIENT_ID = process.env.CF_ACCESS_CLIENT_ID;
const CF_ACCESS_CLIENT_SECRET = process.env.CF_ACCESS_CLIENT_SECRET;
const DIRECTUS_TOKEN = process.env.DIRECTUS_TOKEN;

const getHeaders = () => ({
  'Content-Type': 'application/json',
  // Cloudflare Zero Trust Headers
  'CF-Access-Client-Id': CF_ACCESS_CLIENT_ID,
  'CF-Access-Client-Secret': CF_ACCESS_CLIENT_SECRET,
  // Directus Authentication
  'Authorization': `Bearer ${DIRECTUS_TOKEN}`,
});

export async function fetchArticles() {
  try {
    const response = await fetch(`${DIRECTUS_URL}/items/articles`, {
      headers: getHeaders(),
    });

    if (!response.ok) {
      throw new Error(`Failed to fetch articles: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Error fetching from Directus:', error);
    return { data: [] };
  }
}

3. Stratégie intelligente de cache d’images

Un défi de l’auto-hébergement est la perte des avantages CDN pour les images. Solution : mettre en cache lors du build.

sequenceDiagram
    participant Build as Build Process
    participant CMS as Directus CMS
    participant Cache as Local Cache
    participant CDN as Netlify CDN
    participant User as End User

    Build->>CMS: Fetch articles
    CMS-->>Build: Articles with image URLs
    Build->>Build: Parse image references
    Build->>CMS: Download images
    CMS-->>Build: Image binary data
    Build->>Cache: Store in public/directus-images/
    Build->>Build: Rewrite URLs in content
    Build->>CDN: Deploy static site
    User->>CDN: Request page
    CDN-->>User: Serve with CDN-cached images

Implémentation :

// scripts/fetch-images.js 

import fs from 'fs-extra';
import path from 'path';
import fetch from 'node-fetch';
import {
    config
} from 'dotenv';
config();
async function downloadImage(imageId, outputPath) {
    const imageUrl = `${process.env.DIRECTUS_URL}/assets/${imageId}`;
    const response = await fetch(imageUrl, {
        headers: {
            'CF-Access-Client-Id': process.env.CF_ACCESS_CLIENT_ID,
            'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET,
        }
    });
    if (!response.ok) {
        throw new Error(`Failed to download image ${imageId}`);
    }
    const buffer = await response.arrayBuffer();
    await fs.writeFile(outputPath, Buffer.from(buffer));
    return `/directus-images/${path.basename(outputPath)}`;
}
async function processArticleImages(article) {
    // Extract image IDs from markdown content 
    const imageRegex = /!\[([^\]]*)\]\(([a-f0-9-]{36})\)/gi;
    let match;
    while ((match = imageRegex.exec(article.content)) !== null) {
        const imageId = match[2];
        const localPath = await downloadImage(imageId, `./public/directus-images/${imageId}.jpg`);
        // Rewrite URL in content
        article.content = article.content.replace(`](${imageId})`, `](${localPath})`);
    }
    return article;
}

Analyse des coûts : réduction de 95 %

Avant : écosystème Webflow

Service Coût mensuel Coût annuel Notes
Webflow CMS Plan 35 € 420 € Un site
Site client A 35 € 420 € Chaque client a besoin de son plan
Site client B 35 € 420 € Problème de scalabilité
Domaine personnalisé 0 € 0 € Inclus
Total (3 sites) 105 € 1 260 € Limité à 3 projets

Après : infrastructure auto-hébergée

Service Coût mensuel Coût annuel Notes
Électricité Home Lab ~5 € 60 € Coût incrémental
Cloudflare Zero Trust 0 € 0 € Gratuit (50 utilisateurs max)
Netlify 0 € 0 € Gratuit suffisant
Noms de domaine 1,50 € 18 € 3 domaines
Licence Directus 0 € 0 € Open source
Total (sites illimités) 6,50 € 78 € Projets illimités

Économie annuelle : 1 182 € (−93,8 %)

Performances

La nouvelle architecture ne fait pas qu’économiser de l’argent — elle performe mieux :

graph TB
    subgraph "Webflow"
        A[TTFB: 340ms]
        B[FCP: 1.8s]
        C[LCP: 2.4s]
        D[Score: 76]
    end

    subgraph "New Architecture"
        E[TTFB: 45ms]
        F[FCP: 0.6s]
        G[LCP: 0.9s]
        H[Score: 98]
    end

    style E fill:#9f9,stroke:#333
    style F fill:#9f9,stroke:#333
    style G fill:#9f9,stroke:#333
    style H fill:#9f9,stroke:#333
  • Time to First Byte : amélioration de 87 %
  • First Contentful Paint : +67 %
  • Largest Contentful Paint : +63 %
  • Score Lighthouse : 98/100 (au lieu de 76)

Développement accéléré grâce à l’IA

Un game-changer de cette migration a été l’usage de Claude Code pour le prototypage et l’implémentation rapide.

Comparaison de timeline

Approche classique : ~2 semaines
Avec Claude Code : 3 jours

Claude Code a permis de :

  • Générer du boilerplate pour l’API Directus
  • Implémenter correctement la sécurité du premier coup
  • Créer le système de cache images avec gestion d’erreurs
  • Écrire les playbooks Ansible pour le multi-tenant

Leçons apprises

Points forts

  1. Zero Trust : sécurité entreprise sans complexité
  2. Génération statique : meilleures perfs, coûts réduits, sécurité renforcée
  3. Automatisation Ansible : déploiements homogènes
  4. Développement assisté par IA : vitesse x5

Défis & solutions

Défi Impact initial Solution
Perf images sans CDN Chargement lent Cache au build vers Netlify CDN
Isolation multi-tenant Risques sécurité Containers Docker + réseau isolé
Stratégie de backup Risque de perte Snapshots B2 quotidiens
Monitoring Manque de visibilité Uptime Kuma + Grafana

Évolutions futures

L'architecture actuelle répond très bien à un usage portfolio et à quelques clients. Mais si la charge augmente, deux axes d’évolution s’imposent.

  1. Scalabilité de Directus

Aujourd’hui, Directus tourne en instance unique. À mesure que le nombre d’utilisateurs, de projets et de contenus augmente, il faudra prévoir :
Scalabilité horizontale : plusieurs containers Directus derrière un reverse proxy (ex. Nginx ou Traefik).
Cache Redis : pour partager le cache Directus entre les instances.
Load balancing : équilibrage du trafic entre instances, tolérance de panne.

Cette approche permet de conserver la logique multi-tenant tout en garantissant performance et disponibilité.

  1. CDN intégré à Directus
    Actuellement, les images sont téléchargées pendant le build Astro puis mises en cache côté Netlify.
    Pour un scale plus important, on peut :
    Déléguer le traitement au CMS : Directus intègre un moteur de transformations d’assets (taille, format, compression).
    Brancher directement un CDN (Cloudflare Images, Cloudinary, AWS Cloudfront…) sur le storage configuré dans Directus (S3/Scaleway).

Servir les médias via CDN sans repasser par le build : Astro ne fait plus que référencer les URLs optimisées.

Cela réduit la complexité de la pipeline de build et améliore la rapidité de mise en ligne du contenu.

graph TB
    subgraph "Users"
        U[End Users]
        A[Editors - Directus UI]
    end

    subgraph "CDN Layer"
        C[Global CDN
(Cloudflare/Imgix/Bunny)] end subgraph "Directus Cluster" D1[Directus Instance 1] D2[Directus Instance 2] D3[Directus Instance N] DB[(PostgreSQL DB)] S3[(Object Storage)] end subgraph "Build & Deploy" B[Astro Build] N[Netlify Deploy] end A --> D1 A --> D2 A --> D3 D1 --> DB D2 --> DB D3 --> DB D1 --> S3 D2 --> S3 D3 --> S3 S3 --> C B --> N N --> U C --> U
  • Phase 2 : cache Redis des réponses API
  • Phase 3 : fédération GraphQL multi-sources
  • Phase 4 : edge functions pour personnalisation dynamique

Conclusion : reprendre la maîtrise de sa stack

Cette migration de Webflow vers un CMS auto-hébergé m’a appris que :

  1. Le SaaS n’est pas toujours la meilleure réponse
  2. La sécurité n’implique pas de compromis : Zero Trust le prouve
  3. L’automatisation est clé : Ansible fait gagner des heures
  4. L’IA accélère vraiment : −80 % de temps d’implémentation

Résultat :

  • 95 % moins cher qu’en SaaS
  • 3x plus performant
  • Scalable à l’infini pour les projets clients
  • Sécurité entreprise maintenue

Et surtout : une fondation solide, non seulement pour mon portfolio, mais aussi pour des projets clients, prototypes et expérimentations. Mon home lab est devenu le socle d’un CDN professionnel.


Cette architecture alimente non seulement mon portfolio jpoutrin.dev, mais sert aussi de blueprint pour des CMS économiques, sécurisés et scalables, du projet perso au déploiement entreprise.

Publié le 14 septembre 2025

Mis à jour le 26 septembre 2025