Le genre de phrase qui fait lever un sourcil en équipe, c’est « on a gagné 60% de temps de chargement ». On pense tout de suite CDN, edge cache, refacto héroïque, ou “optimisation” un peu floue. Sauf que dans beaucoup d’apps (Laravel ou pas), le gain est beaucoup plus terre-à-terre : on charge trop de trucs, trop tôt.
Le cas qui m’a donné envie d’écrire cet article vient d’un retour d’expérience côté Laravel Cloud : une page de dashboard est passée d’environ 907 ms à 354 ms. Pas en changeant l’infra. En corrigeant des N+1, en arrêtant d’envoyer des données redondantes, et en différant le chargement du non-essentiel après le rendu principal. Et ça, c’est reproductible dans ton app, même si tu n’utilises pas Nightwatch.
Le vrai « hack » perf : rendre la page rapide, pas “la requête” rapide
Je vois souvent le même piège : on optimise une requête SQL, on gagne 30 ms, on s’applaudit… alors que la page attend toujours 600 ms parce qu’on sérialise un monstre en JSON, qu’on déclenche 80 requêtes annexes en douce, et qu’on bloque l’affichage sur un panneau secondaire du dashboard.
Ce qui compte, c’est le chemin complet. Le serveur met combien de temps à répondre (TTFB), combien de temps à construire la réponse (DB + CPU), quelle taille fait le payload, et surtout : qu’est-ce qui est vraiment nécessaire pour afficher l’écran “utile” au user ? Le reste, c’est souvent du confort dev transformé en dette perf.
Sponsorisé par Le Scribouillard
Besoin de contenu optimisé SEO ?
Utilisez la meilleure plateforme française de création de contenu assistée par IA ! Et générez des articles pour moins de 1€ !
Mesurer proprement avant d’optimiser : sinon tu vas te raconter des histoires
Avant de toucher au code, tu as besoin d’un avant/après qui tient debout. Pas « chez moi ça va plus vite ». Concrètement, je cherche une mesure stable côté serveur (durée de requête HTTP, durée DB cumulée, nombre de requêtes, taille de réponse), et je la regarde en distribution, pas en one-shot.
Si tu as un outil d’observabilité (Nightwatch, Datadog, New Relic, Sentry Performance…), parfait : tu peux sortir des p50/p95, isoler la route, et corréler avec le nombre de requêtes SQL. Sinon, tu peux déjà faire un boulot correct avec des logs structurés, un peu de sampling, et un profilage local pour comprendre le “pourquoi”. L’important, c’est de fixer un protocole. Même simple. Même un peu moche. Mais constant.
N+1 sous Laravel : le bug perf le plus rentable à corriger
Un N+1, ce n’est pas « beaucoup de requêtes », c’est une boucle qui déclenche une requête par item. Sur un dashboard, ça se voit vite : tu charges 50 environnements, et tu fais 50 requêtes pour les derniers déploiements, 50 pour les compteurs, 50 pour les owners… et tu finis avec 200 requêtes pour afficher une page qui aurait dû en faire 10.
Le truc qui rend le N+1 vicieux avec Eloquent, c’est qu’il se cache dans des accessors, des resources, des computed attributes, ou des relations appelées “par confort” dans la vue. Et le pire, c’est que ça passe en staging tant que tu n’as pas des volumes réalistes.
Mon approche est basique : je pars de la route lente en prod, je regarde le nombre de requêtes et les doublons, puis je remonte le callstack côté app. Le fix, c’est presque toujours une histoire de with(), de préchargement ciblé, ou de requête agrégée au bon endroit.
// Avant : N+1 caché (1 query pour les projects + 1 query par project pour lastDeploy)
$projects = Project::query()->latest()->take(50)->get();
return ProjectResource::collection($projects);
// Dans le Resource (ou un accessor) :
// $this->lastDeploy->created_at
// Après : préchargement explicite
$projects = Project::query()
->latest()
->with(['lastDeploy:id,project_id,created_at,status'])
->take(50)
->get();Petit détail qui change tout : précharger, oui, mais précharger le minimum. Si tu fais un with() qui ramène 20 colonnes blobs et 3 relations profondes “au cas où”, tu as juste déplacé le problème vers le payload et la sérialisation.
Réduire le payload : la perf qui ne se voit pas dans EXPLAIN mais qui plombe tout
Une page peut être lente sans requêtes “lentes”. Juste parce que tu envoies trop de données. Et sous Laravel, c’est facile de tomber dedans : tu retournes un modèle Eloquent complet, avec des relations chargées, avec des attributs calculés, et tu laisses un Resource transformer ça en JSON énorme. Ça marche. Jusqu’au jour où tu regardes le poids de la réponse et tu te demandes pourquoi le dashboard fait la taille d’une landing page avec 40 images.
Ce que je coupe en premier, c’est le « payload de confort ». Les champs « on ne sait jamais », les relations renvoyées “par habitude”, les listes complètes là où un count suffit, les objets imbriqués alors que le front n’affiche que deux labels. Je préfère une API explicite et maigre, quitte à rajouter un endpoint si un écran secondaire a besoin de plus.
Autre point sous-estimé : la sérialisation. Entre hydrater des modèles, calculer des attributs, exécuter des casts, passer dans des Resources, puis encoder en JSON, tu peux brûler pas mal de CPU. Si ton endpoint est “DB ok mais CPU haut”, ce n’est pas un mystère, c’est ton code qui fabrique un truc trop riche.
La stratégie “defer” : arrêter de bloquer l’écran sur du non-critique
Le plus gros gain “ressenti” vient souvent de là : tu identifies ce qui est nécessaire pour afficher l’écran principal, et tu refuses de bloquer dessus tout le reste. Un dashboard typique a toujours des panneaux secondaires : activité récente, historiques, suggestions, stats détaillées, logs… C’est utile, mais ce n’est pas nécessaire pour que l’utilisateur comprenne « où il en est ».
Le principe est simple : tu renvoies un payload initial minimal qui permet d’afficher la structure et les infos critiques, puis tu charges le reste via un appel asynchrone une fois le rendu fait. Ça peut être du htmx, du Turbo, une route JSON, un composant Livewire, Inertia, peu importe. L’idée n’est pas “SPA vs SSR”, l’idée c’est de prioriser.
Je le dis cash : si ton endpoint principal attend un graphe complet + des logs + des métriques calculées, tu as choisi de faire attendre tout le monde pour un bloc qui intéresse 10% des sessions. Et après tu cherches des optimisations exotiques. Le defer, c’est souvent la décision la plus rentable.
// Controller : réponse initiale maigre
public function show(Environment $env)
{
$env->load(['project:id,name', 'owner:id,name']);
return response()->json([
'env' => [
'id' => $env->id,
'name' => $env->name,
'project' => $env->project,
'owner' => $env->owner,
],
'summary' => $this->summary($env), // rapide
// Le reste part sur des endpoints dédiés
]);
}
// Endpoint “defer” : logs/activité (lent, mais non-bloquant)
public function activity(Environment $env)
{
return ActivityResource::collection(
$env->activities()->latest()->limit(50)->get()
);
}Le piège classique, c’est de transformer le defer en festival de calls. Tu veux différer, pas saucissonner au hasard. Dans la vraie vie, je regroupe par “panneaux” ou par “besoins métier”, et je fais attention au cache (HTTP, applicatif, ou même juste un cache court) pour éviter de recréer une page qui spamme la DB.
Éviter la rechute : le N+1 revient toujours si tu ne mets aucun garde-fou
Les régressions perf, c’est rarement intentionnel. C’est juste un dev qui rajoute un champ dans un Resource, une relation dans une vue, un computed attribute sympa… et bam, tu repasses de 20 requêtes à 200. Le plus frustrant, c’est que ça n’apparaît pas dans les tests classiques.
Ce que je trouve le plus efficace, c’est d’avoir des budgets simples et assumés sur les routes critiques. Pas un truc militaire sur tout le produit, juste sur les endpoints qui font tourner le business. Nombre max de requêtes, durée p95, taille max de réponse. Et quand ça dépasse, on creuse.
Si tu es sur Laravel, pense aussi à l’outillage local. Debugbar, Telescope, et les outils d’observabilité en prod te donnent une visibilité que tu n’auras jamais “à l’intuition”. L’intuition, c’est bien pour écrire du code. Pour diagnostiquer une perf, je préfère les chiffres.
Présenter le résultat : parle en p50/p95, en TTFB, en payload… pas en « ça va mieux »
Quand tu annonces « -60% », tu dois pouvoir répondre à deux questions sans bégayer. D’abord : “mesuré comment ?”. Ensuite : “pour qui ?”. Si tu ne regardes que ton meilleur run, tu fais du storytelling. Si tu regardes p50 et p95, tu parles produit.
Dans le cas Laravel Cloud, l’histoire est intéressante parce qu’elle combine plusieurs leviers concrets : moins de requêtes, moins de données inutiles, et une page qui rend plus vite parce qu’elle n’attend pas tout. C’est exactement le genre d’amélioration qu’un PM comprend, qu’un dev peut maintenir, et qui résiste mieux au temps qu’un micro-tweak SQL.
Conclusion : vise une page « utile vite », pas une page « complète tard »
Les gros gains de performance viennent rarement d’un outil magique. Ils viennent d’un choix : arrêter de construire une réponse “parfaite” et commencer à construire une réponse priorisée. Tu t’attaques aux N+1, tu mets ton payload au régime, tu différés ce qui n’est pas critique, et tu mesures en p50/p95 pour rester honnête.
Si tu fais ça sur une ou deux pages stratégiques, tu vas souvent te surprendre toi-même. Et surtout, tu vas arrêter de compenser avec de l’infra ce qui est, au fond, un problème de design applicatif.