Nouveau : ENERGIEDIN propose désormais des offres adaptées à tous les budgets, pour les petites, moyennes et grandes entreprises !
11 leviers pour booster les performances : serveur, cache, base de données, modules et CDN. Explications techniques et accessibles.
Avant de plonger dans la technique, comprenons pourquoi la vitesse est cruciale pour votre boutique en ligne.
La vitesse de votre site n'est pas qu'une question technique — c'est un levier business majeur :
Google affirme utiliser la vitesse comme critère de classement depuis 2010 (desktop) et 2018 (mobile). Concrètement :
Il existe deux types de vitesse à ne pas confondre :
Pourquoi cet ordre ? Si votre serveur met 5 secondes à répondre, compresser vos images ne changera rien. On commence toujours par les fondations.
Si LSCache a la page en cache → réponse immédiate (~50ms)
Sinon → PHP + MySQL doivent générer la page (~200-500ms optimisé, 2-5s non optimisé)
Ce guide présente une méthodologie concrète d'optimisation, testée en conditions réelles. Elle s'applique à toutes les versions de PrestaShop (1.6, 1.7, 8 et 9). Plutôt que d'appliquer des optimisations génériques trouvées sur le web, nous allons identifier vos vrais problèmes grâce à un monitoring en temps réel, puis les corriger de manière ciblée.
📌 Cet article est écrit par un développeur.
Il traite uniquement de la performance réelle PrestaShop côté serveur
(TTFB, PHP, MySQL, cache, infrastructure).
Il ne traite pas :
— de scores PageSpeed ou Lighthouse
— d'optimisation "cosmétique" frontend
— de plugins miracles ou solutions marketing
| Vitesse réelle (cet article) | Score PageSpeed (partie 2) | |
|---|---|---|
| Ce qu'on mesure | Temps de réponse du serveur (TTFB), exécution PHP, requêtes SQL | Rendu visuel, taille des ressources, expérience perçue |
| Où ça se passe | Serveur (backend) | Navigateur (frontend) |
| Optimisations | Cache, OPcache, MySQL, index, Memcached, LiteSpeed | Images WebP, lazy loading, CSS critique, Brotli |
| Impact UX / Conversion | Indirect (temps d'attente perçu) | Direct (fluidité visuelle, réactivité aux clics) |
| Priorité | 🥇 À faire EN PREMIER | 🥈 À faire ensuite |
| Métrique | ❌ Mauvais | ⚠️ Moyen | ✅ Bon | 🚀 Excellent |
|---|---|---|---|---|
| TTFB (Time To First Byte) | > 1s | 500ms - 1s | 200ms - 500ms | < 200ms |
| Temps total (HTML généré) | > 3s | 1s - 3s | 500ms - 1s | < 500ms |
curl -w "TTFB: %{time_starttransfer}s" https://www.soudure.pro/ → 0.051s (= 51ms)
Avant toute optimisation, mesurez votre état actuel. Ce travail peut être réalisé gratuitement avec l'outil analyse-seo.net. Vous pouvez aussi ouvrir un terminal et lancer ces 3 commandes (remplacez l'URL par votre site) :
# 1. TTFB page d'accueil (cache chaud - après 2-3 requêtes)
curl -w "TTFB: %{time_starttransfer}s | Total: %{time_total}s\n" -o /dev/null -s https://votre-site.com/
# 2. TTFB page produit (cache contourné avec timestamp)
curl -w "TTFB: %{time_starttransfer}s | Total: %{time_total}s\n" -o /dev/null -s "https://votre-site.com/un-produit?nocache=$(date +%s)"
# 3. Vérifier si LSCache est actif
curl -sI https://votre-site.com/ | grep -i "x-litespeed-cache"
| Commande | Ce qu'elle mesure | Valeur cible |
|---|---|---|
| #1 Cache chaud | Performance maximale (page cachée) | < 100ms avec LSCache, < 500ms sans |
| #2 Cache contourné | Performance réelle PHP/MySQL | < 500ms (bon), < 1s (acceptable) |
| #3 LSCache | Le cache serveur est-il actif ? | Doit afficher "hit" ou "miss" |
Ce schéma représente exactement ce qui se passe côté serveur quand un visiteur demande une page. Imprimez-le mentalement, c'est la clé pour comprendre toutes les optimisations.
Version détaillée du pipeline :
Visiteur / Bot | v DNS / Cloudflare | v Serveur Web (LiteSpeed / Apache) | ├──► LSCache ? │ | │ ├── HIT → HTML servi directement (20–100 ms) ✅ FIN │ | │ └── MISS → exécution complète 👇 | v Bootstrap PrestaShop (init.php, autoload, config) | ├─ Chargement 100–300 fichiers PHP ├─ Lecture configuration (DB) ├─ Construction du contexte (langue, devise, client, panier) | ⏱️ 30–150 ms (OPcache chaud, VPS/dédié) ⏱️ 100–300 ms (mutualisé) ⏱️ 200–700 ms (sans OPcache ❌) | v Modules ACTIFS | ├─ Fichiers chargés ├─ Hooks enregistrés | ⏱️ 50–400 ms selon nombre/qualité des modules | v Exécution des hooks (displayHeader, displayHome, etc.) | ├─ Code PHP modules ├─ Appels SQL | ⏱️ Variable → souvent le vrai goulot | v MySQL | ├─ Memcached ? │ ├─ HIT → réponse immédiate │ └─ MISS → requête SQL | ├─ Index présent ? │ ├─ OUI → index lookup (rapide) │ └─ NON → full table scan ❌ | ⏱️ 20 ms → 2000 ms (selon index) | v Smarty | ├─ Templates compilés ? │ ├─ Oui → cache utilisé │ └─ Non → recompilation + I/O disque ❌ | ⏱️ 20–100 ms | v HTML généré | v Compression (Brotli / Gzip) | v Réponse HTTP envoyée | v Navigateur
LSCache court-circuite 100% de la chaîne PHP/MySQL/Smarty pour les pages publiques.
Quand le cache est HIT, rien d'autre n'est exécuté.
👉 Ces pages passent TOUJOURS par tout le pipeline.
Energiedin intervient principalement sur la vitesse serveur des sites PrestaShop — là où se joue la performance réelle ressentie par les utilisateurs, bien au-delà des scores marketing.
Si TTFB lent, posez-vous ces 3 questions :
| Pages publiques lentes ? | → Problème de cache HIT faible : cookies, variations, exclusions LSCache. Section 8 |
| Panier/checkout lent ? | → Problème hooks/modules sur actionCartSave, displayShoppingCart, etc. Section 9 |
| Admin lent ? | → Souvent MySQL (requêtes stats) + modules back-office. Section 2 |
| Étape | Temps typique | Optimisation | Gain |
|---|---|---|---|
| Serveur web | 5-20ms | LiteSpeed + LSCache | Élimine tout le reste (pages publiques) |
| Bootstrap PHP | 30-300ms | OPcache | Significatif (mesurez avant/après) |
| Modules | 50-400ms | Désinstaller les inutiles | Variable selon modules retirés |
| Hooks | Variable | Profiler + supprimer | Souvent le plus gros gain |
| MySQL | 20-2000ms | Index + Memcached | Très variable (full scan → index) |
| Smarty | 20-100ms | "Jamais recompiler" | Élimine les I/O disque |
Ces gains sont des ordres de grandeur observés sur des boutiques réelles (stack LiteSpeed + PHP 8.x). Ils varient selon thème, modules, catalogue, trafic et CPU. Mesurez avant/après sur votre propre installation.
| Action | Métrique impactée | Gain typique | Cas réel |
|---|---|---|---|
| OPcache activé | Temps compilation PHP | Élimine la recompilation | TTFB : variable selon bootstrap |
| Index MySQL ajouté | Temps de LA requête ciblée | Variable (full scan → index lookup) | Ex mesuré : 1200ms → 20ms |
| LSCache activé | TTFB pages publiques en cache | Très significatif | Ex mesuré : ~1000ms → 50ms (cache HIT) |
| Bot Fight Mode | Charge serveur | Variable (à mesurer) | Dépend de la part de bots malveillants |
| Memcached | Requêtes MySQL répétitives | Variable selon ce qui est caché | Effet sur TTFB : modéré |
| Modules désinstallés | Temps bootstrap | Variable | Dépend des modules (stats, sliders = gros gains) |
| "Jamais recompiler" | I/O disque (stat() sur fichiers) | Élimine les vérifications | Gain faible mais systématique |
| Images WebP | Poids des images | -25 à -35% vs JPEG optimisé | Impact sur temps de chargement perçu |
PrestaShop 1.6 et 1.7 :
| PrestaShop | 5.6 | 7.0 | 7.1 | 7.2 | 7.3 | 7.4 | 8.0+ |
|---|---|---|---|---|---|---|---|
| 1.6.1.x | ✓ | ✓ | ★ | ✗ | ✗ | ✗ | ✗ |
| 1.7.0 ~ 1.7.3 | ✓ | ✓ | ★ | ✗ | ✗ | ✗ | ✗ |
| 1.7.5 ~ 1.7.6 | ✓ | ✓ | ✓ | ★ | ✗ | ✗ | ✗ |
| 1.7.7 | ✗ | ✗ | ✓ | ✓ | ★ | ✗ | ✗ |
| 1.7.8 | ✗ | ✗ | ✓ | ✓ | ✓ | ★ | ✗ |
PrestaShop 8.x :
| PrestaShop | ≤7.1 | 7.2 | 7.3 | 7.4 | 8.0 | 8.1 | ≥8.2 |
|---|---|---|---|---|---|---|---|
| 8.0 ~ 8.2 | ✗ | ✓ | ✓ | ✓ | ✓ | ★ | ✗ |
PrestaShop 9.x :
| PrestaShop | ≤8.0 | 8.1 | 8.2 | 8.3 | 8.4 | ≥8.5 |
|---|---|---|---|---|---|---|
| 9.0 | ✗ | ✓ | ✓ | ✓ | ★ | ✗ |
Légende : ✓ = Compatible | ★ = Version recommandée | ✗ = Non compatible
Source : Documentation officielle PrestaShop
Vérifiez ces 6 points en 2 minutes. Si tout est vert, vous êtes déjà bien optimisé.
php -i | grep opcache.enable |
→ doit afficher On |
echo "stats" | nc localhost 11211 |
→ doit répondre (Memcached actif) |
| PrestaShop → Paramètres avancés → Performances | → Cache activé, "Jamais recompiler" |
curl -I votre-site.com | grep -i x-litespeed-cache |
→ doit afficher hit (LSCache) ⚠️ 1ère requête = miss, c'est normal. Relancez 2×. |
| Cloudflare → Speed → Overview | → CDN actif, Brotli On |
curl -o /dev/null -s -w "%{time_starttransfer}" https://votre-site.com/ |
→ TTFB < 200ms = excellent |
Un point rouge ? Suivez la section correspondante ci-dessous.
| Type | Ce que vous pouvez faire | Ce que vous ne pouvez PAS faire |
|---|---|---|
| Mutualisé (OVH, Ionos...) |
Sections 4, 5, 9, 10, 12-14 PrestaShop, modules, Cloudflare, images |
Sections 1-3, 6-8 Pas d'accès SSH, pas de config PHP/MySQL |
| VPS / Cloud (o2switch, Scaleway...) |
Tout (avec accès root) | - |
| Dédié / cPanel (WHM, Plesk...) |
Tout + optimisations serveur avancées | - |
💡 Conseil : Sur mutualisé, les gains sont limités. Si votre TTFB dépasse 500ms malgré les optimisations possibles, envisagez un VPS (à partir de 5€/mois).
📌 Mutualisé sans SSH ? Ce guide couvre serveur + application. Sautez directement aux sections 4, 5, 9, 10 et 12-14. Les sections 1-3 et 6-8 nécessitent un accès SSH ou root.
Ne jamais tester en production. Avant toute modification :
# Backup rapide avant modifications
mysqldump -u user -p votre_base > backup_$(date +%Y%m%d_%H%M).sql
tar -czf backup_files_$(date +%Y%m%d_%H%M).tar.gz public_html/
# Rollback si problème
mysql -u user -p votre_base < backup_XXXXXXXX.sql
💡 Astuce staging : Softaculous (cPanel) permet de cloner un site en 1 clic. Sinon : sous-domaine + copie fichiers + dump SQL → import.
| Pages publiques (cache HIT) | TTFB < 200ms |
| Pages dynamiques (panier, checkout, compte) | TTFB < 500ms |
Ce que ce guide vous permet de faire :
| PrestaShop 1.6.1.x | → PHP 7.1 (recommandé) |
| PrestaShop 1.7.x | → Varie : 7.1 à 7.4 selon sous-version |
| PrestaShop 8.x | → PHP 8.1 (recommandé) |
| PrestaShop 9.x | → PHP 8.4 (recommandé) |
Avant toute optimisation, on doit comprendre l'état actuel du serveur. Voici les commandes essentielles :
# Charge système et mémoire
uptime
free -h
# Espace disque
df -h /
# Processus les plus gourmands en CPU
ps aux --sort=-%cpu | head -15
# Processus les plus gourmands en mémoire
ps aux --sort=-%mem | head -15
| Symptôme observé | Cause probable | Section |
|---|---|---|
| TTFB > 500ms sur toutes les pages | OPcache absent ou mal configuré | Section 6 |
| TTFB lent uniquement sur certaines pages | Requêtes SQL sans index ou module lent | Section 2 + Section 9 |
| Ajout au panier très lent (> 1s) | Module avec hook cart mal optimisé | Section 9 (audit de code) |
| Site rapide puis lent après quelques heures | Buffer Pool trop petit ou tables trop grosses | Section 2 + Section 4 |
| Load average élevé, CPU à 100% | Bots / attaques ou OPcache absent | Section 10 + Section 6 |
| Swap utilisé > 1GB | Manque de RAM, trop de processus PHP | Augmenter RAM ou réduire MaxProcesses |
| Pages produit lentes, listing rapide | Module sur hook displayProductAdditionalInfo | Section 9 |
| Tout est lent sauf l'admin | LSCache actif en admin mais cache HIT=0 en front | Section 8 |
2. Audit MySQL en temps réelC'est l'étape la plus importante. On va identifier les requêtes qui posent problème pendant que le site est sous charge.
Optimiser la vitesse d'un site PrestaShop nécessite des compétences de développement backend, bien au-delà du marketing ou des outils de scoring. C'est ce type d'intervention technique qu'Energiedin réalise au quotidien.
# Se connecter à MySQL et activer le monitoring
mysql -e "SET GLOBAL slow_query_log = 'ON';"
mysql -e "SET GLOBAL long_query_time = 0.5;"
mysql -e "SET GLOBAL log_queries_not_using_indexes = 'ON';"
Utilisez un outil comme analyse-seo.net pour générer du trafic intensif pendant que vous surveillez MySQL. L'audit en temps réel permet d'identifier les requêtes problématiques sous charge réelle.
# Voir les requêtes en cours d'exécution
mysql -e "SHOW FULL PROCESSLIST\G"
# Charge système
cat /proc/loadavg
# Nombre de connexions MySQL
mysql -e "SHOW STATUS LIKE 'Threads_connected';"
# Top des temps de requête les plus longs
grep "Query_time:" /var/lib/mysql/slow.log | awk '{print $3}' | sort -rn | head -20
# Requêtes SELECT les plus fréquentes
grep -E "^SELECT" /var/lib/mysql/slow.log | sort | uniq -c | sort -rn | head -15
# Requêtes qui examinent beaucoup de lignes (full table scan)
grep -B5 "Rows_examined: [0-9]\{4,\}" /var/lib/mysql/slow.log | grep -E "^SELECT"
# Buffer pool hit ratio (doit être > 99%)
mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';"
# Calcul : (read_requests - reads) / read_requests * 100
mysql -e "SET GLOBAL slow_query_log = 'OFF';"
mysql -e "SET GLOBAL long_query_time = 10;"
mysql -e "SET GLOBAL log_queries_not_using_indexes = 'OFF';"
Une fois les requêtes problématiques identifiées, on analyse leur plan d'exécution avec EXPLAIN.
# Exemple avec une requête identifiée comme fréquente
mysql votre_base -e "EXPLAIN SELECT rate, reviews_nb
FROM ps_steavisgarantis_average_rating
WHERE product_id = 12345 AND id_lang = 1;"
| Type | Signification |
|---|---|
ALL |
Full table scan - mauvais, à corriger |
ref |
Utilise un index - bon |
const |
Accès direct - optimal |
Un covering index inclut toutes les colonnes de la requête. MySQL n'a plus besoin d'accéder à la table, il lit uniquement l'index.
# Pour la requête : SELECT rate, reviews_nb WHERE product_id = X AND id_lang = Y
# On crée un index qui couvre product_id, id_lang ET les colonnes SELECT
mysql votre_base -e "CREATE INDEX idx_perf_rating
ON ps_steavisgarantis_average_rating(product_id, id_lang, rate, reviews_nb);"
# Vérifier que l'index est utilisé (doit afficher "Using index")
mysql votre_base -e "EXPLAIN SELECT rate, reviews_nb
FROM ps_steavisgarantis_average_rating
WHERE product_id = 12345 AND id_lang = 1;"
type: ref (ou range/const selon la requête) avec Extra: Using index au lieu de type: ALL. Note : type: index = full index scan, ce n'est pas optimal.
# Après création d'index, analyser les tables
mysql votre_base -e "ANALYZE TABLE ps_steavisgarantis_average_rating,
ps_product, ps_product_lang, ps_specific_price;"
EXPLAIN que l'index est bien utilisémysql votre_base -e "SELECT table_name,
ROUND(data_length/1024/1024,2) as data_mb,
table_rows
FROM information_schema.tables
WHERE table_schema='votre_base'
ORDER BY data_length DESC
LIMIT 15;"
| Table | Contenu | Action |
|---|---|---|
ps_log |
Logs applicatifs PrestaShop | Garder 30 jours max |
ps_pagenotfound |
Erreurs 404 | Vider complètement |
ps_connections |
Connexions visiteurs | Garder 90 jours max |
# Nettoyer les logs de plus de 30 jours
mysql votre_base -e "DELETE FROM ps_log
WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);"
# Vider la table des erreurs 404
mysql votre_base -e "TRUNCATE TABLE ps_pagenotfound;"
# Récupérer l'espace disque
mysql votre_base -e "OPTIMIZE TABLE ps_log, ps_pagenotfound;"
| Paramètre | Valeur recommandée | Explication |
|---|---|---|
| Mode debug | Non | Le mode debug ralentit fortement le site |
| Recompiler les templates | Jamais recompiler | En production, les templates ne changent pas |
| Cache | Oui | Active le système de cache PrestaShop |
| Combinaison CCC | Tout activer | Combine et minifie CSS/JS |
Dans la section CCC, activez :
Cette option PrestaShop est obsolète depuis HTTP/2. Elle servait à paralléliser les téléchargements via plusieurs sous-domaines. Avec HTTP/2 (supporté par LiteSpeed), le multiplexing rend cette technique inutile. Laissez ces champs vides.
OPcache met en cache le bytecode PHP compilé. C'est l'optimisation avec le meilleur rapport effort/gain.
php -m | grep -i opcache
# Installer OPcache pour PHP 8.1
yum install -y ea-php81-php-opcache
# Redémarrer le serveur web
systemctl restart lsws
[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=2
opcache.validate_timestamps=1
Memcached stocke les objets PHP et résultats de requêtes en mémoire, réduisant la charge sur MySQL.
| Memcached | Redis | |
|---|---|---|
| Complexité | Simple, peu de config | Plus polyvalent, plus de paramètres |
| Persistance | Non (volatile) | Oui (optionnel) |
| Structures | Clé/valeur uniquement | Listes, sets, hashes... |
| Pour PrestaShop | Supporté nativement (intégré au core) | Nécessite un module tiers |
Verdict : Ce guide utilise Memcached car il est supporté nativement par PrestaShop (option dans Paramètres avancés > Performances). Redis nécessite un module tiers (ex: "Redis Cache" sur Addons) et une configuration supplémentaire. Pour un site PrestaShop standard, Memcached suffit largement. Redis est pertinent si vous avez d'autres applications qui l'utilisent déjà (Laravel, file de jobs, sessions partagées, etc.).
# Installer Memcached et l'extension PHP
yum install -y memcached ea-php81-php-memcached
# Démarrer et activer le service
systemctl enable memcached
systemctl start memcached
# Redémarrer LiteSpeed
systemctl restart lsws
Éditez /etc/sysconfig/memcached pour allouer plus de mémoire :
# Passer de 64MB à 512MB
CACHESIZE="512"
# Redémarrer Memcached
systemctl restart memcached
# Vérifier que ça fonctionne
echo "stats" | nc localhost 11211 | head -5
127.0.0.1112111rm -rf var/cache/*) et rafraîchissez la page.
| Critère | Apache | LiteSpeed |
|---|---|---|
| Consommation mémoire | Élevée (fork par connexion) | Faible (event-driven) |
| Visiteurs simultanés | Limité par la RAM | Milliers sans problème |
| Cache intégré | Non (nécessite Varnish) | Oui (LSCache) |
| Compatibilité .htaccess | Native | 100% compatible |
| HTTP/3 & QUIC | Non | Oui |
| Prix | Gratuit | Inclus chez la plupart des hébergeurs cPanel |
LSCache est le vrai atout de LiteSpeed pour PrestaShop. Il met en cache les pages entières au niveau du serveur, avant même que PHP ne soit exécuté.
# Dans le terminal, vérifier les headers HTTP
curl -I https://votre-site.com/ | grep -i x-litespeed-cache
# Vous devez voir :
# X-LiteSpeed-Cache: hit (page servie depuis le cache)
# X-LiteSpeed-Cache: miss (page générée puis mise en cache)
LSCache ne peut pas tout cacher. Comprendre quelles pages sont cachées est essentiel :
| Type de page | Exemples | Cachée par LSCache ? | Goulot d'étranglement réel |
|---|---|---|---|
| Pages publiques | Accueil, catégories, fiches produits, CMS | ✅ Oui | Aucun si cache actif (50-100ms) |
| Pages semi-dynamiques | Recherche, filtres, pagination | ⚠️ Partiel | MySQL (requêtes de recherche) |
| Pages privées | Mon compte, historique commandes | ❌ Non | Bootstrap + MySQL |
| Pages transactionnelles | Panier, checkout, paiement | ❌ Non | Bootstrap + modules paiement |
| Ajax/API | Ajout panier, mise à jour quantité | ❌ Non | PHP + hooks modules |
LSCache crée des variations de cache selon différents critères. Trop de variations = cache inefficace :
| Critère de variation | Comportement | Impact sur le cache |
|---|---|---|
| Langue | 1 version cachée par langue | Acceptable (nécessaire) |
| Devise | 1 version cachée par devise | Acceptable (nécessaire) |
| Groupe client | 1 version par groupe (grossiste, particulier...) | ⚠️ Attention si > 3 groupes |
| Cookies tracking | Peut créer 1 version par visiteur ! | ❌ Critique — exclure ces cookies du cache |
| Paramètres GET | ?utm_source, ?gclid créent des variations | ❌ Configurer LSCache pour ignorer ces paramètres |
# Vérifier les variations de cache dans les headers
curl -I "https://votre-site.com/" | grep -i "vary"
# "Vary: Accept-Encoding" → Normal
# "Vary: Cookie" → ⚠️ Peut fragmenter le cache
curl -X PURGE ne fonctionne que si la méthode PURGE est autorisée dans votre configuration LiteSpeed (désactivée par défaut sur certains hébergeurs).
LiteSpeed et LSCache ne remplacent pas les autres optimisations, ils les complètent :
Avant même d'exécuter la moindre logique métier, PrestaShop doit :
Coût du bootstrap (varie selon CPU, I/O, OPcache, nombre de modules) :
Pour mesurer votre bootstrap réel : activez _PS_DEBUG_PROFILING_ temporairement et consultez le rapport en bas de page.
Un module désactivé conserve :
| Action | Fichiers PHP | Tables SQL | Hooks | Crons |
|---|---|---|---|---|
| Désactiver | Restent | Restent | Retirés | Peuvent subsister |
| Désinstaller | Restent (sauf si supprimés) | Supprimées (généralement) | Retirés | Supprimés |
| Désinstaller + supprimer | Supprimés | Supprimées | Retirés | Supprimés |
| Module | Problème | Alternative |
|---|---|---|
| Statistiques PrestaShop (natifs) | Requêtes SQL lourdes, calculs en temps réel | Google Analytics / Matomo |
| Modules de paiement inactifs | Hooks inutiles au checkout | Désinstaller complètement |
| Sliders / Carrousels lourds | JavaScript bloquant, images non optimisées | Image statique ou CSS uniquement |
| Modules SEO redondants | Plusieurs modules qui font la même chose | Un seul module SEO bien configuré |
| ps_eventbus / ps_accounts | Appels HTTP synchrones vers les serveurs PrestaShop | Désactiver si vous n'utilisez pas PrestaShop Metrics |
| Modules de partage social | Scripts externes, tracking tiers | Simples liens HTML vers les réseaux |
Activez le mode debug temporairement pour voir le temps d'exécution par hook :
# Dans config/defines.inc.php, temporairement :
define('_PS_MODE_DEV_', true);
define('_PS_DEBUG_PROFILING_', true);
# Rechargez une page front-office
# En bas de page, vous verrez le temps passé dans chaque hook/module
# Identifiez les modules qui prennent > 50ms
# IMPORTANT : Remettez à false après le test !
define('_PS_MODE_DEV_', false);
define('_PS_DEBUG_PROFILING_', false);
Si vous avez des modules développés sur mesure ou des overrides, ils peuvent contenir des problèmes de performance invisibles.
# Lister tous les overrides
ls -la override/classes/
ls -la override/controllers/
# Trouver les modules avec overrides
find modules/ -name "*.php" -path "*/override/*" -type f
# Vérifier les hooks sur le panier
mysql votre_base -e "SELECT h.name, m.name
FROM ps_hook h
JOIN ps_hook_module hm ON h.id_hook = hm.id_hook
JOIN ps_module m ON hm.id_module = m.id_module
WHERE h.name LIKE '%cart%' OR h.name LIKE '%Cart%'
ORDER BY h.name;"
| ❌ Anti-pattern | Problème | ✅ Solution |
|---|---|---|
Db::getInstance()->executeS() dans une boucle |
N+1 queries | Une seule requête avec IN() ou JOIN |
Product::getProducts() répété |
Même requête exécutée plusieurs fois | Mettre en cache dans une variable static |
| Appels HTTP synchrones (cURL, file_get_contents) | Bloque l'exécution, timeout possible | Appels asynchrones ou en cron |
SELECT * au lieu de colonnes spécifiques |
Charge des données inutiles | Sélectionner uniquement les colonnes nécessaires |
| Pas de LIMIT sur les requêtes | Peut charger des millions de lignes | Toujours paginer avec LIMIT |
| Calculs complexes dans les hooks fréquents | Exécuté à chaque requête | Précalculer et stocker en BDD ou cache |
// ❌ Mauvais : N+1 requêtes
foreach ($product_ids as $id) {
$product = new Product($id);
$names[] = $product->name;
}
// ✅ Bon : 1 seule requête
$sql = 'SELECT id_product, name FROM '._DB_PREFIX_.'product_lang
WHERE id_product IN ('.implode(',', array_map('intval', $product_ids)).')
AND id_lang = '.(int)$id_lang;
$products = Db::getInstance()->executeS($sql);
// ❌ Mauvais : appelé plusieurs fois
public function hookDisplayHome() {
$products = Product::getProducts($id_lang, 0, 10, 'id_product', 'ASC');
// ...
}
// ✅ Bon : cache statique
private static $cachedProducts = null;
public function hookDisplayHome() {
if (self::$cachedProducts === null) {
self::$cachedProducts = Product::getProducts($id_lang, 0, 10, 'id_product', 'ASC');
}
$products = self::$cachedProducts;
// ...
}
Dans le dashboard Cloudflare :
Full (strict)Bot Fight ModeLe Bot Fight Mode bloque automatiquement les robots malveillants (scrapers, spammeurs, scanners de vulnérabilités). Selon le Cloudflare Radar Year in Review 2025, les bots représentent ~53% des requêtes HTML :
Autrement dit, plus de la moitié des requêtes sur votre serveur peuvent venir de robots. Bloquer les bots malveillants libère des ressources pour vos vrais clients.
Bot Fight Mode peut potentiellement bloquer certains services légitimes (API de paiement, flux Google...). Surveillez vos logs les premiers jours et créez des règles d'exception si nécessaire :
Dans Security → WAF → Custom Rules, créez une règle :
Nom : Autoriser API Paiement
Si : (http.request.uri.path contains "/module/") and (http.request.uri.path contains "paiement" or http.request.uri.path contains "payment" or http.request.uri.path contains "cmcic" or http.request.uri.path contains "paypal" or http.request.uri.path contains "stripe")
Action : Skip (All remaining custom rules, Rate limiting, Bot Fight Mode)
Pour que Google puisse accéder à vos flux produits :
Nom : Autoriser Google Merchant
Si : (cf.client.bot) and (http.request.uri.path contains "/flux" or http.request.uri.path contains "/feed" or http.request.uri.path contains "/export" or http.request.uri.path contains ".xml")
Action : Skip (Bot Fight Mode)
Nom : Autoriser bots SEO
Si : (cf.verified_bot_category in {"Search Engine Crawler" "Search Engine Optimization" "Feed Fetcher"})
Action : Allow
# Vérifier que Cloudflare est actif
curl -I https://votre-site.com/ | grep -i cf-ray
# Vérifier que Brotli est activé
curl -sI -H "Accept-Encoding: br" https://votre-site.com/ | grep -i content-encoding
# Doit afficher : content-encoding: br
Après activation de LSCache, Cloudflare, ou modification de règles WAF, testez immédiatement :
⚠️ Si vous ne testez pas ça, vous pouvez perdre des ventes sans le voir.
Une règle WAF mal configurée = paiements bloqués silencieusement, panier vide sans explication, flux Google inaccessible.
→ Testez en navigation privée + mobile après chaque modification.
Après avoir appliqué toutes les optimisations, vérifiez ces métriques :
# OPcache actif
php -i | grep "opcache.enable"
# Memcached fonctionne
echo "stats" | nc localhost 11211 | grep "bytes"
# MySQL buffer pool hit ratio
mysql -e "SELECT
ROUND((1 - (
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') /
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests')
)) * 100, 2) AS hit_ratio_percent;"
# TTFB page d'accueil (cache chaud)
curl -w "TTFB: %{time_starttransfer}s\n" -o /dev/null -s https://votre-site.com/
# Objectif : < 200ms
# TTFB page produit (cache contourné)
curl -w "TTFB: %{time_starttransfer}s\n" -o /dev/null -s "https://votre-site.com/un-produit?nocache=$(date +%s)"
# Objectif : < 500ms
# Vérifier LSCache
curl -sI https://votre-site.com/ | grep -i x-litespeed-cache
# Objectif : hit
| Métrique | Objectif | Comment vérifier |
|---|---|---|
| TTFB (cache HIT) | < 100ms | curl ou GTmetrix / analyse-seo.net |
| TTFB (cache MISS) | < 500ms | curl ?nocache ou WebPageTest |
| Buffer Pool Hit Ratio | > 99% | phpMyAdmin → SQL (section 2) |
| Slow queries | Minimal | phpMyAdmin ou cPanel → MySQL |
| Load average | < nb CPU | cPanel → Server Status |
# Nettoyage mensuel des logs PrestaShop (le 1er à 3h)
0 3 1 * * mysql votre_base -e "DELETE FROM ps_log WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);"
# Nettoyage hebdomadaire des 404 (dimanche à 4h)
0 4 * * 0 mysql votre_base -e "TRUNCATE TABLE ps_pagenotfound;"
Suivez cet ordre pour un maximum d'efficacité avec un minimum de risques :
💡 Conseil : Faites une modification à la fois et mesurez l'impact avant de passer à la suivante.
Maintenant que votre serveur répond en moins de 200ms, optimisons le rendu frontend.
| Format | Compression | Transparence | Support | Usage |
|---|---|---|---|---|
| JPEG | Bonne | ❌ Non | 100% | Photos (fallback) |
| PNG | Moyenne | ✅ Oui | 100% | Logos uniquement |
| WebP | Très bonne (-30%) | ✅ Oui | 97% | ✅ À utiliser partout |
| AVIF | Excellente (-50%) | ✅ Oui | 92% | Si supporté |
Le lazy loading retarde le chargement des images hors écran. Seules les images visibles sont chargées immédiatement.
<!-- Méthode native HTML (moderne) -->
<img src="produit.webp" alt="Produit" loading="lazy" />
<!-- PrestaShop 8+ : activé par défaut sur les listes produits -->
<!-- PrestaShop 1.7 : module requis ou modification thème -->
| Métrique | Mesure | ✅ Bon | ⚠️ À améliorer | ❌ Mauvais |
|---|---|---|---|---|
| LCP Largest Contentful Paint |
Temps d'affichage du plus grand élément visible | ≤ 2.5s | 2.5s - 4s | > 4s |
| INP Interaction to Next Paint |
Réactivité aux clics/taps | ≤ 200ms | 200ms - 500ms | > 500ms |
| CLS Cumulative Layout Shift |
Stabilité visuelle (éléments qui "sautent") | ≤ 0.1 | 0.1 - 0.25 | > 0.25 |
Déjà couvert en section 5 : combine et minifie les fichiers CSS/JS.
Le CSS critique contient uniquement les styles nécessaires pour afficher le contenu visible sans scroll. Il est injecté directement dans le HTML.
<!-- Dans le <head>, AVANT les fichiers CSS externes -->
<style>
/* CSS critique : header, menu, premier écran */
.header { ... }
.hero { ... }
</style>
<!-- CSS complet chargé en différé -->
<link rel="preload" href="theme.css" as="style" onload="this.rel='stylesheet'">
<!-- Bloquant (mauvais) -->
<script src="script.js"></script>
<!-- Async : charge en parallèle, exécute dès que prêt (pour scripts indépendants) -->
<script src="analytics.js" async></script>
<!-- Defer : charge en parallèle, exécute après le HTML (recommandé) -->
<script src="theme.js" defer></script>
/* Éviter le flash de texte invisible (FOIT) */
@font-face {
font-family: 'MaPolice';
src: url('mapolice.woff2') format('woff2');
font-display: swap; /* Affiche le texte immédiatement avec une police système */
}
| Score | Interprétation | Action |
|---|---|---|
| 90-100 | Excellent | Maintenez ce niveau |
| 50-89 | Correct, améliorable | Optimisez les points signalés |
| 0-49 | Problématique | Priorité haute |
Brotli est un algorithme de compression développé par Google. Selon les benchmarks officiels, il offre une compression ~15-25% supérieure à Gzip pour les fichiers texte (HTML, CSS, JS). Il est activé par défaut sur LiteSpeed et Cloudflare.
# Vérifier si Brotli est actif
curl -I -H "Accept-Encoding: br" https://votre-site.com/ | grep -i content-encoding
# Résultat attendu :
content-encoding: br
| ❌ "Optimisation" | Pourquoi c'est inutile | ✅ Ce qu'il faut faire à la place |
|---|---|---|
| PHP 8.1 → 8.2/8.3 sans OPcache | Gain mineur (~5-10%). Sans OPcache, PHP recompile tout à chaque requête. | Installer OPcache d'abord |
| Activer Redis/Memcached sans audit SQL | Vous cachez des requêtes lentes. Le problème reste. | Audit MySQL + index, PUIS Memcached |
| Augmenter la RAM serveur sans index | MySQL utilisera plus de RAM pour faire des full scan. | Créer les index manquants d'abord |
| Micro-optimiser Smarty alors que MySQL rame | Smarty = 20-100ms. MySQL sans index = 500-2000ms. | Optimiser MySQL en priorité |
| CDN sans cache serveur | Le CDN cache les assets statiques, pas le HTML. Si le serveur met 2s à répondre, le CDN ne change rien. | LSCache d'abord, CDN ensuite |
| Compresser les images avec un TTFB de 3s | L'utilisateur attend 3s AVANT de voir quoi que ce soit. | TTFB < 500ms d'abord, images ensuite |
| Serveurs média PrestaShop | Obsolète avec HTTP/2 (multiplexing). | Laisser vide, utiliser un vrai CDN si besoin |
| Désactiver les modules au lieu de désinstaller | Le code reste sur le serveur, les tables et crons peuvent subsister. | Désinstaller complètement |
| Viser 100/100 PageSpeed | Un score de 85 avec TTFB 50ms est mieux qu'un 100 avec TTFB 800ms. | Prioriser le ressenti utilisateur réel |
| Métrique | 🎯 Objectif "suffisant" | Au-delà = rendements décroissants |
|---|---|---|
| TTFB | < 200ms | En dessous, les gains deviennent marginaux pour l'expérience perçue |
| Temps total (HTML) | < 500ms | Suffisant pour une excellente expérience utilisateur |
| Load average | < nb CPU | Si stable à 0.5 avec 2 CPU, inutile d'optimiser plus |
| Buffer pool hit ratio | > 99% | À 99.5%, pas besoin de viser 99.9% |
| Score PageSpeed mobile | > 80 | La différence 80 vs 95 n'impacte pas significativement l'UX ni les signaux Google |
| LCP | < 2.5s | C'est le seuil Google "Good". En dessous, c'est du bonus. |
Une fois les seuils atteints, passez en mode maintenance :
Parfois, les outils classiques (slow query log, profilers) ne suffisent pas à identifier un goulot d'étranglement. La solution : instrumenter le code avec des logs de timing précis. Voici un cas réel où l'ajout au panier prenait 2 secondes.
L'ajout au panier via POST /panier prenait systématiquement 2.1 secondes. Aucune requête SQL lente visible, le serveur n'était pas surchargé. Le problème était invisible.
On crée un utilitaire minimaliste qui mesure le temps entre deux points :
// modules/cartperflog.php
class CartPerfLog
{
private static $startTimes = [];
private static $logs = [];
private static $globalStart = null;
private static $logFile = '/tmp/cart_perf.log';
public static function start($name)
{
if (self::$globalStart === null) {
self::$globalStart = microtime(true);
}
self::$startTimes[$name] = microtime(true);
$elapsed = round((microtime(true) - self::$globalStart) * 1000, 2);
file_put_contents(self::$logFile, "[+{$elapsed}ms] START: {$name}\n", FILE_APPEND);
}
public static function end($name)
{
if (!isset(self::$startTimes[$name])) return;
$duration = round((microtime(true) - self::$startTimes[$name]) * 1000, 2);
$elapsed = round((microtime(true) - self::$globalStart) * 1000, 2);
file_put_contents(self::$logFile, "[+{$elapsed}ms] END: {$name} ({$duration}ms)\n", FILE_APPEND);
self::$logs[$name] = $duration;
}
public static function summary()
{
$total = round((microtime(true) - self::$globalStart) * 1000, 2);
arsort(self::$logs);
file_put_contents(self::$logFile, "\nTOP 10 plus lents:\n", FILE_APPEND);
$i = 0;
foreach (self::$logs as $name => $duration) {
if ($i++ >= 10) break;
$pct = round(($duration / $total) * 100, 1);
file_put_contents(self::$logFile, " {$duration}ms ({$pct}%) - {$name}\n", FILE_APPEND);
}
}
}
On ajoute des logs à chaque étape potentiellement lente. Pour le panier, on crée un override de CartController :
// override/controllers/front/CartController.php
require_once _PS_MODULE_DIR_ . 'cartperflog.php';
class CartController extends CartControllerCore
{
protected function processChangeProductInCart()
{
CartPerfLog::start('processChangeProductInCart()');
CartPerfLog::start('validation');
// ... code de validation ...
CartPerfLog::end('validation');
CartPerfLog::start('updateQty [CRITICAL]');
$this->context->cart->updateQty(...);
CartPerfLog::end('updateQty [CRITICAL]');
CartPerfLog::end('processChangeProductInCart()');
CartPerfLog::summary();
}
}
Le premier run montre que updateQty() prend 741ms. On instrumente alors Cart::updateQty() :
// Résultat après instrumentation de updateQty() :
[+85ms] START: this->update() [SAVE]
[+718ms] END: this->update() [SAVE] (633ms) ← 633ms !
Le problème est dans $this->update(). On continue à creuser :
// On découvre que ObjectModel::update() déclenche des hooks.
// Modules enregistrés sur actionObjectCartUpdateAfter :
// - ps_eventbus
// - nxtalwishlist
// - alma
En désactivant ps_eventbus temporairement :
| Métrique | Avant | Après | Gain |
|---|---|---|---|
| update() [SAVE] | 633ms | 4.65ms | -628ms |
| TOTAL /panier | 2177ms | 374ms | -1800ms |
SELECT h.name, m.name FROM ps_hook h JOIN ps_hook_module hm... WHERE h.name LIKE '%Cart%'L'optimisation de PrestaShop n'est pas une science exacte. La clé est de mesurer avant d'optimiser. L'audit MySQL en temps réel permet d'identifier les vrais problèmes plutôt que d'appliquer des optimisations génériques qui n'auront peut-être aucun impact sur votre configuration spécifique.
📗 Partie 1 — Serveur (faire EN PREMIER) :
📘 Partie 2 — Frontend (faire ENSUITE) :
Testez toujours les modifications sur un environnement de staging avant de les appliquer en production.
Cet article reflète notre manière de travailler chez Energiedin : partir du serveur, comprendre les contraintes techniques réelles, et ne pas confondre indicateurs marketing et performance vécue.
La vitesse PrestaShop ne se corrige pas avec un plugin, mais avec une compréhension complète de la stack.