← tous les projets
folioroprojet· 11 min de lecture

Folioro

Créez votre portfolio professionnel en quelques clics

Lux

✦ Folioro ✦

Créez votre portfolio professionnel en quelques clics

Choisissez un template, renseignez vos infos, téléchargez un portfolio HTML prêt à publier.
Aucun compte, aucune dépendance côté utilisateur, un seul fichier à héberger où vous voulez.

Node.js Express SQLite Stripe Vercel

19 templates · 9 gratuits · 10 premium à 3 € · Export HTML autonome

Démarrage rapide · Templates · Architecture · API · Déploiement


📖 Sommaire


✨ Aperçu

Folioro est un générateur de portfolio en ligne, en français, pensé pour les développeurs, designers, freelances et créatifs. Le parcours tient en trois étapes :

ÉtapeCe qui se passe
1 · ChoisirGalerie de 19 templates avec aperçu plein écran, en mode bureau ou mobile.
2 · RemplirNom, titre, bio, photo (ou initiales générées), compétences, projets, liens (email, GitHub, LinkedIn, site).
3 · ExporterAperçu en direct, puis téléchargement de portfolio.html ou copie du code en un clic.

Le fichier généré est 100 % autonome : HTML et CSS embarqués, rien à installer, il suffit de le déposer sur GitHub Pages, Netlify, un serveur perso ou une clé USB.


🚀 Fonctionnalités

Côté utilisateur

  • 🎨 19 templates aux identités très différentes (glassmorphism, brutalisme, éditorial, japonais, steampunk…)
  • 👁 Aperçu modal de chaque template avec données d'exemple, bascule bureau / mobile
  • ✍️ Formulaire guidé : compétences multiples séparées par des virgules, projets ajoutables et supprimables à volonté
  • 🧑 Avatar automatique à partir des initiales si aucune photo n'est fournie
  • ⬇️ Export instantané : téléchargement du .html ou copie dans le presse-papier
  • 👑 Templates premium à 3 € (paiement unique via Stripe), restaurables sur n'importe quel appareil avec l'email d'achat
  • 🔄 Déblocage synchronisé entre onglets : payez dans un onglet, le template se débloque dans l'autre
  • ⚖️ Mentions légales, CGU et politique de confidentialité intégrées

Côté propriétaire

  • 🔒 Génération premium côté serveur : le code des templates payants n'est jamais envoyé au navigateur
  • 📊 Tableau de bord admin avec graphiques (Chart.js) : visiteurs, géographie, générations, entonnoir d'étapes, activité récente
  • 🌍 Géolocalisation IP avec cache SQLite (via ip-api.com)
  • 🔐 Auth admin par cookie HttpOnly signé HMAC, mots de passe hachés en scrypt
  • 🔍 SEO prêt : balises Open Graph / Twitter Card, données structurées JSON-LD, robots.txt, sitemap.xml

🎨 Les templates

Gratuits

TemplateStyleIdéal pourTags
AuroraFuturiste & élégantDev / DesignerDark Gradient Glass
BlancMinimaliste & épuréCréatif / FreelanceLight Minimal Serif
NeoCyberpunk & hackerDev / TechDark Neon Mono
TerraChaleureux & naturelIllustrateur / ArtisteWarm Earthy Round
WaveColoré & dynamiqueCréatif / ProductColorful Bold Modern
BrutalNéo-brutaliste & audacieuxArtiste / Indie devBold Brutal Yellow
ZenMinimalisme japonaisDesigner / PhotographeMinimal Elegant Calm
PearlInspiré Apple, épuréTous profilsWhite Apple
SolarpunkNature & technologie, radieuxProfils engagésBright Nature Green

Premium · 3 € en paiement unique

TemplateStyleTags
PrismGlassmorphism extrême, ultra premiumDark Glass 3D
LumièreÉditorial luxe inspiré haute coutureLight Luxury Editorial
NoirÉditorial mode, haute tension graphiqueBlack Editorial Bold
SoleilLuxe méditerranéen, élégance doréeWarm Gold Luxury
CosmosNébuleuse spatiale, éthéré & futuristeSpace Nebula Dark
VelvetOpulence pourpre, haute couturePurple Gold Opulent
MarbleArchitecture éditoriale, marbre & rigueurWhite Marble Architecture
SakuraMinimalisme japonais, douceur & floraisonPink Japan Soft
ArchitectureGrille suisse, structuré & précisMinimal Swiss Grid
SteampunkMécanique victorienne, cuivre & engrenagesDark Victorian Copper
note

Architecture et Steampunk n'ont pas encore de lien Stripe : la modale affiche « Paiement disponible prochainement ».


⚡ Démarrage rapide

Prérequis : Node.js 18 ou plus récent et npm. better-sqlite3 est un module natif, une chaîne de compilation (build-essential, Xcode CLT ou Visual Studio Build Tools) peut être nécessaire si aucun binaire précompilé n'existe pour votre plateforme.

# 1. Récupérer le projet
git clone <url-du-repo> folioro && cd folioro

# 2. Installer les dépendances
npm install

# 3. Configurer l'environnement
cp .env.example .env   # ou créez .env à la main (voir Configuration)

# 4. Lancer
npm start
  Folioro server → http://localhost:3000
  Admin panel   → http://localhost:3000/admin

Au premier lancement, le serveur crée data/admin.db et le compte administrateur défini dans .env.

astuce

Sans clé Stripe, tout fonctionne en local : les templates gratuits se génèrent normalement, seule la vérification des achats premium est désactivée.


⚙️ Configuration

Toute la configuration passe par des variables d'environnement, chargées depuis .env grâce à dotenv.

VariableRequiseRôle
PORTPort d'écoute (défaut : 3000)
TOKEN_SECRET✅Secret HMAC qui signe les jetons d'achat et la session admin
ADMIN_USERNAME✅Identifiant du compte admin créé au premier démarrage
ADMIN_PASSWORD✅Mot de passe de ce compte (haché en scrypt dans admin.db)
STRIPE_SECRET_KEY⚠️Clé secrète Stripe, nécessaire pour vérifier les achats premium
STRIPE_WEBHOOK_SECRET⚠️Secret de signature du webhook Stripe (fortement recommandé en production)

Modèle à enregistrer en .env.example (versionné) puis copier en .env :

PORT=3000
TOKEN_SECRET=remplacez-moi-par-une-longue-chaine-aleatoire
ADMIN_USERNAME=admin
ADMIN_PASSWORD=un-mot-de-passe-solide
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

Générer un secret solide :

node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
important

Le compte admin n'est créé que s'il n'existe pas encore. Changer ADMIN_PASSWORD après le premier lancement ne modifie pas le mot de passe enregistré : supprimez la ligne correspondante dans admin_users (ou data/admin.db) pour le recréer.


🏗 Architecture

Folioro est une application monopage en JavaScript natif (aucun framework, aucun bundler) servie par un petit serveur Express.

Diagramme (source ci-dessous)
Source du diagramme
flowchart LR
    subgraph Navigateur
        UI["index.html<br/>+ js/app.js"]
        FT["Templates gratuits<br/>js/templates/*.js"]
        LS[("localStorage<br/>pf_unlocked · pf_token")]
    end

    subgraph Serveur Express
        API["server.js"]
        PT["Templates premium<br/>server/templates/*.js"]
        DB[("SQLite<br/>data/admin.db")]
        PJ[("data/purchases.json")]
    end

    Stripe(("Stripe"))
    GEO(("ip-api.com"))

    UI --> FT
    UI <--> LS
    UI -- "/api/generate · /api/preview" --> API
    API --> PT
    API --> DB
    API --> PJ
    API <-- "vérification d'achat" --> Stripe
    Stripe -- "webhook" --> API
    API -. "géolocalisation" .-> GEO

Comment un template est rendu

  • Chaque template est un module qui s'enregistre dans window.Templates avec ses métadonnées (name, description, tags, previewHTML…) et une fonction generate(data) qui renvoie le HTML complet.
  • Gratuit : generate() s'exécute directement dans le navigateur.
  • Premium : le serveur intercepte /js/templates/<premium>.js et ne renvoie que les métadonnées (generate: null). Le rendu se fait via POST /api/generate, après vérification du jeton d'achat.

💳 Modèle premium & paiement

Diagramme (source ci-dessous)
Source du diagramme
sequenceDiagram
    actor U as Utilisateur
    participant F as Front (app.js)
    participant S as Stripe
    participant B as server.js

    U->>F: Clique sur un template premium
    F->>S: Payment Link (?client_reference_id=<template>)
    S-->>B: Webhook checkout.session.completed
    B->>B: Enregistre email → template (purchases.json)
    U->>F: Saisit son email d'achat
    F->>B: GET /api/verify?email=…
    B->>S: Consulte clients & sessions payées
    B-->>F: { unlocked: [...], token } (valide 30 jours)
    F->>F: Stocke le jeton dans localStorage
    U->>F: Génère son portfolio
    F->>B: POST /api/generate { templateId, data, token }
    B-->>F: HTML du portfolio
  • Les achats sont reconnus de deux façons : le webhook Stripe (stocké dans data/purchases.json) et une interrogation directe de l'API Stripe à la vérification. Un client peut donc restaurer ses templates sur un nouvel appareil avec son seul email.
  • Le jeton renvoyé est signé HMAC-SHA256 avec TOKEN_SECRET et expire après 30 jours.
  • Les liens de paiement sont définis dans PREMIUM_LINKS (js/app.js).

Configurer le webhook Stripe

  1. Dans le dashboard Stripe, créez un endpoint vers https://<votre-domaine>/api/webhook.
  2. Abonnez-le à l'événement checkout.session.completed.
  3. Copiez le secret de signature dans STRIPE_WEBHOOK_SECRET.

En local, avec la CLI Stripe :

stripe listen --forward-to localhost:3000/api/webhook

📊 Tableau de bord admin

Accessible sur /admin (redirection vers /admin/login sans session valide).

BlocContenu
KPIVisiteurs uniques (jour / semaine / mois / total), pages vues, générations, taux de conversion
VisiteursCourbe sur les 30 derniers jours
Templates utilisésRépartition des générations par template
Zones géographiquesTop 15 des pays
Activité récente40 derniers événements (visites, générations, interactions)
EntonnoirTemplates sélectionnés, étapes visitées, générations par template

La session admin est un cookie admin_session HttpOnly, SameSite=Strict, valable 24 h. Les pages du dossier views/ ne sont jamais servies en statique.

Schéma SQLite (data/admin.db, mode WAL)

TableContenu
admin_usersComptes admin (salt:hash scrypt)
visitsIP, pays, ville, user-agent, chemin, référent, horodatage
generationsTemplate généré, IP, horodatage
interactionsÉvénements front (step_viewed, template_selected…)
ip_geocacheCache de géolocalisation par IP

🔌 API

MéthodeRouteDescription
GET/api/verify?email=Retrouve les templates achetés par un email et renvoie { unlocked, token }
GET/api/preview/:idHTML d'aperçu d'un template premium, rempli avec des données d'exemple
POST/api/generateGénère un portfolio premium. Corps : { templateId, data, token }
POST/api/trackEnregistre une interaction. Corps : { type, value }
POST/api/webhookWebhook Stripe (checkout.session.completed)
GET/admin/loginPage de connexion admin
POST/admin/loginConnexion (formulaire), pose le cookie de session
POST/admin/logoutDéconnexion
GET/adminTableau de bord 🔒
GET/admin/api/statsStatistiques agrégées en JSON 🔒
Format des données d'un portfolio
{
  "name": "Sophie Martin",
  "title": "Designer & Développeuse Full-Stack",
  "about": "Passionnée par la création d'expériences numériques…",
  "photo": "",
  "email": "sophie@example.com",
  "github": "github.com/sophiemartin",
  "linkedin": "linkedin.com/in/sophiemartin",
  "website": "sophiemartin.dev",
  "skills": ["React", "TypeScript", "Node.js"],
  "projects": [
    { "id": 1, "name": "NovaCMS", "desc": "Un CMS moderne…", "link": "https://example.com" }
  ]
}

📁 Structure du projet

folioro/
├── index.html              # SPA : galerie, formulaire, export, modales légales
├── js/
│   ├── app.js              # Logique front : étapes, formulaire, aperçu, déblocage, export
│   └── templates/          # 19 templates (les premium n'y servent que de métadonnées)
├── server.js               # Serveur Express : API, admin, analytics, Stripe
├── server/
│   ├── helpers.js          # esc() et initials() côté serveur
│   └── templates/          # Générateurs des 10 templates premium (jamais exposés)
├── views/
│   ├── admin-login.html    # Connexion admin
│   └── admin-dashboard.html# Tableau de bord (Chart.js)
├── api/
│   └── verify.js           # Ancienne fonction serverless Vercel (non routée)
├── data/                   # Créé au lancement : admin.db, purchases.json
├── vercel.json             # Configuration de déploiement Vercel
├── robots.txt
├── sitemap.xml
└── package.json

🧩 Ajouter un template

Template gratuit

  1. Créez js/templates/montheme.js :

    Templates.montheme = {
      id: 'montheme',
      name: 'Mon Thème',
      description: 'Court pitch — Public visé',
      tags: ['Dark', 'Minimal'],
      previewClass: 'prev-montheme',
      previewHTML: `<div>…mini aperçu de la carte…</div>`,
      generate(d) {
        return `<!DOCTYPE html><html lang="fr">…${esc(d.name)}…</html>`;
      }
    };
    
  2. Ajoutez <script src="js/templates/montheme.js"></script> dans index.html, avant js/app.js.

  3. Ajoutez le style de la vignette (.prev-montheme) dans le <style> d'index.html.

attention

Échappez toujours les champs saisis par l'utilisateur avec esc() pour éviter toute injection HTML dans le portfolio généré.

Template premium

  1. Faites de même dans js/templates/ avec premium: true (ce fichier sert de métadonnées).
  2. Créez server/templates/montheme.js qui exporte { meta, generate } en utilisant server/helpers.js.
  3. Ajoutez l'id dans PREMIUM_IDS (server.js).
  4. Créez un Payment Link Stripe et ajoutez-le dans PREMIUM_LINKS (js/app.js).
  5. Sur Vercel, ajoutez l'id dans la route /js/templates/(...) de vercel.json.

🌐 Déploiement

Serveur Node (VPS, Railway, Render, Fly.io…)

C'est l'option recommandée : SQLite et purchases.json ont besoin d'un disque persistant.

npm ci --omit=dev
NODE_ENV=production npm start

Placez le serveur derrière un reverse proxy HTTPS (Nginx, Caddy). L'IP du visiteur est lue depuis X-Forwarded-For.

Vercel

Un vercel.json est fourni : server.js est déployé comme fonction Node, /api/* et les templates premium y sont routés, le reste est servi en statique. Déclarez STRIPE_SECRET_KEY et TOKEN_SECRET (secrets @stripe_secret_key et @token_secret), ainsi que les variables admin.

danger

Sur Vercel, le système de fichiers est éphémère : les statistiques SQLite et purchases.json sont perdues entre deux exécutions. La vérification des achats continue de fonctionner grâce à l'API Stripe, mais le tableau de bord admin ne sera pas fiable. Pour l'analytics, préférez un hébergement avec disque persistant ou une base externe.


🛡 Sécurité

  • Les templates premium sont rendus uniquement côté serveur, leur code source n'est jamais exposé.
  • Mots de passe admin hachés en scrypt avec sel aléatoire, comparaison en temps constant.
  • Jetons et sessions signés HMAC-SHA256, avec expiration.
  • Cookie admin HttpOnly + SameSite=Strict.
  • Webhook Stripe vérifié par signature dès que STRIPE_WEBHOOK_SECRET est défini.
  • Entrées utilisateur échappées dans tous les templates via esc().

Checklist avant mise en production

  • TOKEN_SECRET, ADMIN_USERNAME et ADMIN_PASSWORD définis avec des valeurs fortes
  • STRIPE_WEBHOOK_SECRET renseigné (sinon les webhooks ne sont pas authentifiés)
  • .env et data/ exclus du dépôt (.gitignore)
  • Aucun identifiant par défaut laissé dans le code
  • HTTPS activé devant le serveur

🗺️ Feuille de route

  • Hébergement en 1 clic (offre Starter à 1 €/mois) : mise en ligne sur un sous-domaine, SSL automatique, mises à jour illimitées. Actuellement en bêta privée.
  • Liens de paiement pour Architecture et Steampunk
  • Persistance externe des statistiques pour les déploiements serverless

Fait avec ☕ et beaucoup de CSS.

folioro.com

Markdown supporté · Ctrl+Entrée pour publierSe connecter pour publier

chargement de la discussion…