✦ 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.
19 templates · 9 gratuits · 10 premium à 3 € · Export HTML autonome
Démarrage rapide · Templates · Architecture · API · Déploiement
📖 Sommaire
- Aperçu
- Fonctionnalités
- Les templates
- Démarrage rapide
- Configuration
- Architecture
- Modèle premium & paiement
- Tableau de bord admin
- API
- Structure du projet
- Ajouter un template
- Déploiement
- Sécurité
- Feuille de route
✨ 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 :
| Étape | Ce qui se passe |
|---|---|
| 1 · Choisir | Galerie de 19 templates avec aperçu plein écran, en mode bureau ou mobile. |
| 2 · Remplir | Nom, titre, bio, photo (ou initiales générées), compétences, projets, liens (email, GitHub, LinkedIn, site). |
| 3 · Exporter | Aperç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
.htmlou 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
| Template | Style | Idéal pour | Tags |
|---|---|---|---|
| Aurora | Futuriste & élégant | Dev / Designer | Dark Gradient Glass |
| Blanc | Minimaliste & épuré | Créatif / Freelance | Light Minimal Serif |
| Neo | Cyberpunk & hacker | Dev / Tech | Dark Neon Mono |
| Terra | Chaleureux & naturel | Illustrateur / Artiste | Warm Earthy Round |
| Wave | Coloré & dynamique | Créatif / Product | Colorful Bold Modern |
| Brutal | Néo-brutaliste & audacieux | Artiste / Indie dev | Bold Brutal Yellow |
| Zen | Minimalisme japonais | Designer / Photographe | Minimal Elegant Calm |
| Pearl | Inspiré Apple, épuré | Tous profils | White Apple |
| Solarpunk | Nature & technologie, radieux | Profils engagés | Bright Nature Green |
Premium · 3 € en paiement unique
| Template | Style | Tags |
|---|---|---|
| Prism | Glassmorphism extrême, ultra premium | Dark Glass 3D |
| Lumière | Éditorial luxe inspiré haute couture | Light Luxury Editorial |
| Noir | Éditorial mode, haute tension graphique | Black Editorial Bold |
| Soleil | Luxe méditerranéen, élégance dorée | Warm Gold Luxury |
| Cosmos | Nébuleuse spatiale, éthéré & futuriste | Space Nebula Dark |
| Velvet | Opulence pourpre, haute couture | Purple Gold Opulent |
| Marble | Architecture éditoriale, marbre & rigueur | White Marble Architecture |
| Sakura | Minimalisme japonais, douceur & floraison | Pink Japan Soft |
| Architecture | Grille suisse, structuré & précis | Minimal Swiss Grid |
| Steampunk | Mécanique victorienne, cuivre & engrenages | Dark Victorian Copper |
noteArchitecture 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.
astuceSans 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.
| Variable | Requise | Rôle |
|---|---|---|
PORT | Port 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'))"
importantLe compte admin n'est créé que s'il n'existe pas encore. Changer
ADMIN_PASSWORDaprès le premier lancement ne modifie pas le mot de passe enregistré : supprimez la ligne correspondante dansadmin_users(oudata/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.
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" .-> GEOComment un template est rendu
- Chaque template est un module qui s'enregistre dans
window.Templatesavec ses métadonnées (name,description,tags,previewHTML…) et une fonctiongenerate(data)qui renvoie le HTML complet. - Gratuit :
generate()s'exécute directement dans le navigateur. - Premium : le serveur intercepte
/js/templates/<premium>.jset ne renvoie que les métadonnées (generate: null). Le rendu se fait viaPOST /api/generate, après vérification du jeton d'achat.
💳 Modèle premium & paiement
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_SECRETet expire après 30 jours. - Les liens de paiement sont définis dans
PREMIUM_LINKS(js/app.js).
Configurer le webhook Stripe
- Dans le dashboard Stripe, créez un endpoint vers
https://<votre-domaine>/api/webhook. - Abonnez-le à l'événement
checkout.session.completed. - 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).
| Bloc | Contenu |
|---|---|
| KPI | Visiteurs uniques (jour / semaine / mois / total), pages vues, générations, taux de conversion |
| Visiteurs | Courbe sur les 30 derniers jours |
| Templates utilisés | Répartition des générations par template |
| Zones géographiques | Top 15 des pays |
| Activité récente | 40 derniers événements (visites, générations, interactions) |
| Entonnoir | Templates 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)
| Table | Contenu |
|---|---|
admin_users | Comptes admin (salt:hash scrypt) |
visits | IP, pays, ville, user-agent, chemin, référent, horodatage |
generations | Template généré, IP, horodatage |
interactions | Événements front (step_viewed, template_selected…) |
ip_geocache | Cache de géolocalisation par IP |
🔌 API
| Méthode | Route | Description |
|---|---|---|
GET | /api/verify?email= | Retrouve les templates achetés par un email et renvoie { unlocked, token } |
GET | /api/preview/:id | HTML d'aperçu d'un template premium, rempli avec des données d'exemple |
POST | /api/generate | Génère un portfolio premium. Corps : { templateId, data, token } |
POST | /api/track | Enregistre une interaction. Corps : { type, value } |
POST | /api/webhook | Webhook Stripe (checkout.session.completed) |
GET | /admin/login | Page de connexion admin |
POST | /admin/login | Connexion (formulaire), pose le cookie de session |
POST | /admin/logout | Déconnexion |
GET | /admin | Tableau de bord 🔒 |
GET | /admin/api/stats | Statistiques 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
-
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>`; } }; -
Ajoutez
<script src="js/templates/montheme.js"></script>dansindex.html, avantjs/app.js. -
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
- Faites de même dans
js/templates/avecpremium: true(ce fichier sert de métadonnées). - Créez
server/templates/montheme.jsqui exporte{ meta, generate }en utilisantserver/helpers.js. - Ajoutez l'id dans
PREMIUM_IDS(server.js). - Créez un Payment Link Stripe et ajoutez-le dans
PREMIUM_LINKS(js/app.js). - Sur Vercel, ajoutez l'id dans la route
/js/templates/(...)devercel.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.
dangerSur Vercel, le système de fichiers est éphémère : les statistiques SQLite et
purchases.jsonsont 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_SECRETest défini. - Entrées utilisateur échappées dans tous les templates via
esc().
Checklist avant mise en production
-
TOKEN_SECRET,ADMIN_USERNAMEetADMIN_PASSWORDdéfinis avec des valeurs fortes -
STRIPE_WEBHOOK_SECRETrenseigné (sinon les webhooks ne sont pas authentifiés) -
.envetdata/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.