Si votre PageSpeed vous reproche “Éliminer les ressources CSS bloquantes”, vous avez probablement un gros fichier CSS chargé dans le head… alors que l’utilisateur n’a besoin que de 2 à 10% de ces règles pour voir le haut de page.
Le problème de performance
Le symptôme typique : un site qui “clignote”, un rendu qui arrive tard, et des Core Web Vitals pénalisées (surtout LCP et parfois CLS si des styles critiques arrivent après). Le navigateur doit télécharger, parser et appliquer les CSS avant de peindre le contenu. Un gros CSS global, chargé tôt, devient un goulot d’étranglement.
Sur WordPress 6.9.4 (avril 2026), le cœur a fait progresser le chargement des assets, mais il ne peut pas deviner votre CSS “above-the-fold”. Les thèmes et page builders (Divi 5, Elementor, Avada) ajoutent souvent plusieurs feuilles CSS, parfois conditionnelles, parfois globales, et parfois injectées dynamiquement. J’ai souvent vu des pages à 250–600 KB de CSS cumulés, dont une majorité n’impacte pas le premier écran.
Impact concret :
- SEO : LCP et INP influencent votre visibilité, surtout sur mobile.
- Taux de rebond : sur 4G moyenne, 200–400 ms de CSS bloquant en plus se ressentent immédiatement.
- UX : rendu tardif, flash de contenu non stylé (FOUC), layout instable.
À la fin, vous saurez : extraire un Critical CSS de façon reproductible, l’inliner au bon endroit, charger le CSS non critique en différé, et éviter les conflits classiques avec le cache et les builders.
Résumé rapide
- Vous n’inlinerez pas “tout le CSS” : uniquement ce qui est nécessaire au premier écran.
- Vous générerez le Critical CSS automatiquement (WP-CLI + script headless), puis vous le mettrez en cache par type de page.
- Vous inlinerez via wp_head (priorité haute) et vous différerez le reste via un preload + onload (avec
<noscript>). - Vous éviterez les anti-patterns : inliner 80 KB, casser CSP, oublier le cache, ou différer une feuille vraiment critique (menu, header, fonts).
- Vous mesurerez avec du code : temps serveur, taille CSS, nombre de handles, et validation Lighthouse/CrUX.
Diagnostic avec du code
1) Activer les logs utiles (sans casser la prod)
Sur staging (ou en fenêtre courte sur prod), activez le logging. Évitez WP_DEBUG_DISPLAY en production.
<?php
// wp-config.php (staging recommandé)
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
// Optionnel : log des requêtes (peut ralentir fortement !)
define('SAVEQUERIES', true);
Avec SAVEQUERIES, vous pourrez corréler une page lente à des requêtes DB… même si, ici, le sujet est surtout “rendu bloqué par CSS”.
2) Mesurer ce que WordPress envoie comme CSS (handles, tailles, ordre)
Le diagnostic le plus rentable : lister les styles réellement enqueued et estimer leur poids. Vous pouvez ajouter ce snippet dans un petit plugin mu-plugin pour éviter qu’un thème enfant le perde.
<?php
/**
* Plugin Name: BPCAB - CSS Inspector
* Description: Inspecte les styles enqueued et logge des métriques (WordPress 6.9.4+).
*/
add_action('wp_enqueue_scripts', function () {
if (!current_user_can('manage_options')) {
return;
}
if (!defined('WP_DEBUG') || !WP_DEBUG) {
return;
}
global $wp_styles;
if (!$wp_styles) {
return;
}
$handles = $wp_styles->queue;
$report = [
'count' => count($handles),
'styles' => [],
];
foreach ($handles as $handle) {
$obj = $wp_styles->registered[$handle] ?? null;
if (!$obj) {
continue;
}
$src = $obj->src;
// Normaliser les URLs relatives (cas fréquent avec plugins/builders)
if ($src && !preg_match('~^https?://~', $src)) {
$src = site_url($src);
}
$report['styles'][] = [
'handle' => $handle,
'src' => $src,
'media' => $obj->args ?: 'all',
'deps' => $obj->deps,
];
}
error_log('[BPCAB CSS] ' . wp_json_encode($report));
}, 999);
Ce log vous donne la liste des handles. Ensuite, pour estimer les tailles, deux options réalistes :
- via DevTools/Network (manuel, pas demandé ici),
- via un script headless (voir étape 1) qui télécharge les CSS et calcule leur poids.
3) Query Monitor et logs “slow” côté serveur
Query Monitor reste l’outil le plus rapide pour vérifier si le “lent” vient du serveur ou du front. Installez-le sur staging.
4) Diagnostic WP-CLI (inventaire thèmes/plugins et autoload)
Quand je vois un CSS énorme, je trouve souvent un duo : builder + plugin “bonus” + thème surchargé. Commencez par un inventaire.
# Plugins et tailles (approche simple)
wp plugin list --status=active
# Vérifier les options autoload trop lourdes (peut impacter TTFB)
wp option list --autoload=on --fields=option_name,size --format=table | head -n 50
# Infos thème
wp theme list
Étape 1 : Extraire un Critical CSS fiable (sans “deviner”)
Le Critical CSS utile n’est pas “le CSS du header”. C’est l’ensemble minimal de règles nécessaires pour rendre correctement le premier écran : typographie de base, grille/layout du hero, header/nav, boutons visibles, et parfois quelques états (hover peut attendre, mais pas toujours).
Deux approches existent :
- Approche artisanale : vous écrivez à la main un mini CSS. Ça marche sur un site simple, mais casse vite sur Divi/Elementor/Avada.
- Approche automatisée : headless Chrome + extraction (Penthouse/Critters). C’est celle que je recommande en 2026 si vous voulez quelque chose de maintenable.
Pipeline recommandé (Node + headless)
Objectif : générer un fichier critical-home.css, critical-single.css, etc., à partir d’URLs réelles du site (staging idéalement). J’utilise souvent Penthouse car il sort un critical CSS propre à partir d’une page rendue.
Pré-requis : Node 18+ (ou 20+), et un environnement où Chromium headless peut tourner.
# Dans un dossier tools/critical-css à la racine du projet
npm init -y
npm i penthouse postcss postcss-safe-parser cssnano
Script de génération (exemple minimal mais fonctionnel). Il télécharge la page, calcule le critical, puis minifie.
/**
* tools/critical-css/generate-critical.mjs
* Génère du Critical CSS pour une liste d'URLs.
* Testé pour WordPress 6.9.4+ (avril 2026) avec Node 18+.
*/
import fs from 'node:fs/promises';
import path from 'node:path';
import penthouse from 'penthouse';
import postcss from 'postcss';
import safeParser from 'postcss-safe-parser';
import cssnano from 'cssnano';
const targets = [
{ key: 'front', url: process.env.TARGET_FRONT || 'https://example.test/' },
{ key: 'single', url: process.env.TARGET_SINGLE || 'https://example.test/mon-article/' },
{ key: 'archive', url: process.env.TARGET_ARCHIVE || 'https://example.test/category/actu/' },
];
const outDir = path.resolve(process.cwd(), 'dist');
await fs.mkdir(outDir, { recursive: true });
for (const t of targets) {
// Astuce : choisissez une taille de viewport réaliste mobile + desktop.
// Vous pouvez générer 2 critical et les combiner, mais attention au poids.
const critical = await penthouse({
url: t.url,
css: [
// Option 1 : pointez vers le CSS principal (si stable)
// 'https://example.test/wp-content/themes/votre-theme/style.css',
// Option 2 : laissez penthouse détecter via la page (souvent mieux avec builders)
],
width: 390,
height: 844,
timeout: 60000,
blockJSRequests: false, // Builders : parfois nécessaire pour rendre le layout
renderWaitTime: 200, // Ajustez si animations/JS tardif
});
const minified = await postcss([cssnano({ preset: 'default' })]).process(critical, {
from: undefined,
parser: safeParser,
});
const file = path.join(outDir, `critical-${t.key}.css`);
await fs.writeFile(file, minified.css, 'utf8');
console.log(`OK: ${file} (${minified.css.length} bytes)`);
}
Points à surveiller (retours d’expérience) :
- Sur Elementor/Divi, blockJSRequests à
truedonne souvent un critical incomplet (layout non calculé). Je le laisse fréquemment àfalseet je limite lerenderWaitTime. - Votre page peut charger des CSS conditionnels selon cookies, A/B test, user agent. Générez sur une URL “propre”.
- Ne générez pas un critical “universel” pour tout le site. Faites au moins : front, single, archive, page builder “landing”.
Intégration WP-CLI (option pratique)
Vous pouvez piloter cette génération depuis WP-CLI via une commande custom, mais en pratique je garde le script Node indépendant, et je le déclenche en CI/CD. Si vous tenez à WP-CLI, faites simple : WP-CLI lance le script Node.
<?php
// mu-plugins/bpcab-critical-cli.php
if (defined('WP_CLI') && WP_CLI) {
WP_CLI::add_command('bpcab critical-css', function () {
// Sécurité : ne lancez pas ça en prod sans contrôle.
$cmd = 'node tools/critical-css/generate-critical.mjs';
WP_CLI::log('Exécution: ' . $cmd);
passthru($cmd, $status);
if ($status !== 0) {
WP_CLI::error('Échec génération Critical CSS (code ' . $status . ')');
}
WP_CLI::success('Critical CSS généré dans tools/critical-css/dist/');
});
}
Étape 2 : Inliner le Critical CSS proprement (et charger le reste en différé)
Une fois le critical généré, vous voulez :
- l’inliner dans le
<head>pour débloquer le rendu, - charger les feuilles CSS complètes sans bloquer (ou au moins réduire le blocage).
Code “AVANT” (lent) : CSS global bloquant
Exemple réaliste : un thème qui charge tout en all dans le head.
<?php
add_action('wp_enqueue_scripts', function () {
wp_enqueue_style('theme-style', get_stylesheet_uri(), [], '1.0.0', 'all');
wp_enqueue_style('theme-extra', get_stylesheet_directory_uri() . '/assets/css/extra.css', [], '1.0.0', 'all');
}, 20);
Code “APRÈS” : inline critical + preload du reste
Je préfère une stratégie explicite :
- Inline critical via
wp_headtrès tôt (ou très tard selon votre thème, mais de façon stable). - Transformer certaines feuilles en
preloadpuisrel=stylesheetau onload. - Garder un fallback
<noscript>pour les navigateurs sans JS.
Implémentation sous forme de petit plugin (recommandé) : vous ne voulez pas perdre ce code lors d’un changement de thème.
<?php
/**
* Plugin Name: BPCAB - Critical CSS Inline
* Description: Inline Critical CSS et chargement différé du CSS non critique (WP 6.9.4+, PHP 8.1+).
*/
final class BPCAB_Critical_CSS {
private string $version = '1.0.0';
public function hooks(): void {
add_action('wp_head', [$this, 'print_critical_css'], 20);
add_filter('style_loader_tag', [$this, 'defer_selected_styles'], 10, 4);
}
/**
* Détermine une clé de gabarit simple pour choisir le bon critical.
* Vous pouvez raffiner (template spécifique, ID de page, etc.).
*/
private function get_context_key(): string {
if (is_front_page()) {
return 'front';
}
if (is_singular('post')) {
return 'single';
}
if (is_archive() || is_home()) {
return 'archive';
}
return 'default';
}
public function print_critical_css(): void {
// Évitez l'admin et les feeds.
if (is_admin() || is_feed() || wp_doing_ajax()) {
return;
}
$key = $this->get_context_key();
// Chemin : vous pouvez stocker dans wp-content/uploads/critical-css/
$upload_dir = wp_get_upload_dir();
$base_dir = trailingslashit($upload_dir['basedir']) . 'critical-css/';
$file = $base_dir . 'critical-' . $key . '.css';
if (!file_exists($file)) {
// Fallback : évitez de casser le rendu si le fichier manque.
return;
}
$css = file_get_contents($file);
if (!is_string($css) || $css === '') {
return;
}
// Sécurité/perf : limitez la taille (anti-pattern fréquent : inliner 50-100 KB).
if (strlen($css) > 20000) { // ~20 KB, ajustez selon votre site
// Je préfère ne rien inliner plutôt que d'alourdir le HTML.
return;
}
echo "<style id='bpcab-critical-css'>n" . $css . "n</style>n";
}
/**
* Diffère certaines feuilles via preload/onload.
* Attention : ne différez pas une feuille réellement critique (layout header, fonts, etc.).
*/
public function defer_selected_styles(string $html, string $handle, string $href, string $media): string {
if (is_admin() || is_feed() || wp_doing_ajax()) {
return $html;
}
// Liste blanche : choisissez des handles non critiques.
// J'ai souvent de bons résultats en différant "extras", "icons", "animations", "woocommerce-general" (selon pages).
$defer_handles = [
'theme-extra',
'fontawesome',
'elementor-animations',
];
if (!in_array($handle, $defer_handles, true)) {
return $html;
}
// Ne pas différer pour les utilisateurs connectés si vous avez une barre admin/variantes.
if (is_user_logged_in()) {
return $html;
}
$href_attr = esc_url($href);
// Pattern preload + onload (avec noscript fallback).
// Note : on conserve media="all" côté stylesheet final.
$out = "<link rel='preload' as='style' href='{$href_attr}' />n";
$out .= "<link rel='stylesheet' href='{$href_attr}' media='all' onload="this.onload=null;this.media='all'" />n";
$out .= "<noscript><link rel='stylesheet' href='{$href_attr}' media='all' /></noscript>n";
return $out;
}
}
add_action('plugins_loaded', function () {
$plugin = new BPCAB_Critical_CSS();
$plugin->hooks();
});
Mesure d’impact (réaliste)
Sur des sites “builder” moyens, j’observe souvent :
- réduction de 100 à 400 ms de “render blocking” sur mobile,
- LCP qui descend de 3.2s à 2.4s sur une page lourde (à condition que l’image LCP soit aussi optimisée),
- moins de FOUC si le critical contient bien header + hero.
Le gain exact dépend du réseau, du CPU mobile, et du nombre de CSS bloquants initiaux.
Étape 3 : Mettre en cache par type de page (front, article, archive…)
Générer un critical “à la volée” en PHP est une mauvaise idée : headless Chrome côté serveur = catastrophe en perf et sécurité. La bonne approche : génération offline + stockage fichier + invalidation.
Stockage dans uploads + invalidation simple
Je stocke dans wp-content/uploads/critical-css/ pour éviter qu’un déploiement écrase les fichiers, et je régénère via CI/CD ou manuellement.
Créez le dossier (une fois) :
mkdir -p wp-content/uploads/critical-css
# Vérifiez les permissions du serveur web
Si vous avez un process de déploiement, ajoutez une étape qui copie tools/critical-css/dist/*.css vers uploads/critical-css/.
Option : versionner le critical par “build”
Problème fréquent : vous mettez à jour le thème, mais le critical reste ancien, et vous avez un header cassé pendant des jours. Une solution simple : suffixer le fichier avec une version et stocker la version en option.
<?php
// Exemple : stocker une "version de build" du critical en option.
add_action('update_option_theme_mods_' . get_option('stylesheet'), function () {
// Quand des mods de thème changent, marquez le critical comme potentiellement obsolète.
update_option('bpcab_critical_css_needs_regen', 1, false);
}, 10, 0);
Ce flag ne régénère rien tout seul, mais vous évite d’oublier. Sur un site pro, je déclenche une régénération automatique via CI quand ce flag est présent.
Étape 4 : Réduire le CSS inutile (désenregistrer, scoper, conditionner)
Le Critical CSS aide à débloquer le rendu, mais si vous gardez 600 KB de CSS derrière, vous améliorez LCP tout en gardant un coût réseau/CPU élevé. Le meilleur “optim” reste de ne pas charger.
Désenregistrer un style sur des pages où il ne sert à rien
Cas classique : WooCommerce CSS sur tout le site, ou CSS d’un plugin de formulaires sur des pages sans formulaire.
<?php
add_action('wp_enqueue_scripts', function () {
// Exemple : ne chargez pas un style plugin sur les pages sans besoin.
if (!is_page('contact')) {
wp_dequeue_style('contact-form-7');
wp_deregister_style('contact-form-7');
}
// Exemple WooCommerce : à manier avec prudence (testez toutes les pages boutique).
if (function_exists('is_woocommerce') && !is_woocommerce() && !is_cart() && !is_checkout()) {
wp_dequeue_style('woocommerce-general');
wp_dequeue_style('woocommerce-layout');
wp_dequeue_style('woocommerce-smallscreen');
}
}, 100);
Anti-pattern : dequeuer trop tôt
Erreur que je vois : faire wp_dequeue_style() à priorité 10 alors que le plugin enqueue à 20/99. Résultat : ça ne marche pas, et vous pensez que “WordPress ignore mon code”. Gardez une priorité haute (souvent 100) pour dequeuer.
Minifier et concaténer : à utiliser avec discernement
En 2026, HTTP/2 et HTTP/3 réduisent l’intérêt de la concaténation “à tout prix”. La minification reste utile. Si vous minifiez côté build, gardez une map et une version. Évitez les plugins qui minifient à la volée en PHP sur chaque requête.
Étape 5 : Cas réels Divi 5, Elementor, Avada (et leurs pièges)
Divi 5
Divi peut générer des CSS dynamiques par page/module. Deux pièges :
- le CSS “critical” change dès qu’un module est modifié,
- certaines feuilles sont injectées tardivement (ou conditionnées), donc votre extraction headless doit attendre un peu.
Ce que je fais souvent :
- générer le critical sur une version “stable” de la page (staging),
- inclure dans le critical les styles de header et de la première section uniquement,
- ne pas différer les feuilles Divi principales sans tests, sinon FOUC visible.
Elementor
Elementor a des CSS globaux + CSS par page (selon config). J’ai souvent vu des sites où le CSS par page est déjà une forme de “critical” partiel, mais il reste bloquant. Votre liste blanche de defer doit être prudente : différer “elementor-frontend” peut casser le rendu initial.
Approche que j’applique :
- Inline critical (header + hero) + defer uniquement les extras (animations, icon packs).
- Si Elementor génère un CSS par page, gardez-le en non-différé si c’est le layout above-the-fold.
Avada (Fusion Builder)
Avada charge souvent beaucoup d’options de style global. Le critical CSS marche bien, mais le risque est de “doubler” des règles et d’augmenter le HTML si vous inlinez trop. Sur Avada, je limite strictement le critical (10–15 KB) et je mise plus sur la réduction des feuilles inutiles.
Petit helper : lister les handles par page (utile avec builders)
Ajoutez un paramètre ?cssdebug=1 pour afficher les handles en bas de page (admin seulement). Ça évite d’aller lire les logs.
<?php
add_action('wp_footer', function () {
if (!current_user_can('manage_options')) {
return;
}
if (!isset($_GET['cssdebug']) || $_GET['cssdebug'] !== '1') {
return;
}
global $wp_styles;
if (!$wp_styles) {
return;
}
echo "<div style='padding:12px;border-top:2px solid #000;background:#fff;color:#000;font:12px/1.4 monospace'>";
echo "<strong>CSS handles enqueued</strong><br>";
foreach ($wp_styles->queue as $h) {
echo esc_html($h) . "<br>";
}
echo "</div>";
}, 9999);
Configuration serveur
Le Critical CSS joue surtout sur le rendu, mais si votre serveur envoie des CSS sans cache HTTP correct, vous perdez une partie du bénéfice. Visez : Cache-Control long pour les fichiers versionnés, et compression.
.htaccess (Apache) : cache + compression
# .htaccess (dans le dossier racine WordPress)
# Attention : testez sur staging. Une règle mal placée peut casser le site.
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 30 days"
ExpiresByType application/javascript "access plus 30 days"
ExpiresByType text/javascript "access plus 30 days"
ExpiresByType image/svg+xml "access plus 30 days"
ExpiresByType image/webp "access plus 30 days"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch ".(css|js|svg|webp)$">
Header set Cache-Control "public, max-age=2592000, immutable"
</FilesMatch>
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/css application/javascript text/javascript
</IfModule>
php.ini / PHP-FPM : OPcache (souvent oublié)
OPcache n’accélère pas le CSS, mais réduit le CPU PHP et libère du budget pour le reste. Sur beaucoup de petits serveurs, c’est un gain immédiat.
; php.ini (exemple)
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
Vérification des résultats
Vous voulez vérifier trois choses : (1) moins de CSS bloquant, (2) rendu plus rapide, (3) pas de régression visuelle.
1) Vérifier que le critical est bien inline et petit
Ajoutez une mesure simple : taille du critical inline (dans le HTML). En admin seulement.
<?php
add_action('wp_head', function () {
if (!current_user_can('manage_options')) {
return;
}
// Mesure naïve : présence de la balise (utile pour valider rapidement)
echo "<meta name='bpcab-critical-css' content='enabled' />n";
}, 1);
2) Mesurer le temps côté PHP (TTFB approximatif) + mémoire
Ça ne mesure pas LCP, mais ça vous évite de confondre “CSS bloquant” et “serveur lent”.
<?php
add_action('wp_footer', function () {
if (!current_user_can('manage_options')) {
return;
}
$time = timer_stop(0, 3);
$mem = size_format(memory_get_peak_usage(true));
echo "<div style='padding:10px;background:#111;color:#fff;font:12px/1.4 monospace'>";
echo "Temps PHP (approx) : " . esc_html($time) . "s<br>";
echo "Mémoire pic : " . esc_html($mem);
echo "</div>";
}, 9999);
3) Lighthouse en CLI (reproductible)
Le plus pratique : lancer Lighthouse en headless, avant/après, et comparer le “render-blocking resources” et le LCP.
# Exemple (nécessite Chrome/Chromium)
npm i -g lighthouse
lighthouse https://example.test/
--only-categories=performance
--preset=mobile
--output=json
--output-path=./lh-front.json
Si les performances ne s’améliorent pas
Quand le critical CSS ne change presque rien, c’est rarement “parce que critical CSS ne marche pas”. C’est souvent un autre goulot d’étranglement qui domine.
1) LCP dominé par l’image (cas le plus fréquent)
Si l’élément LCP est une image hero de 800 KB, le critical CSS ne suffira pas. Ajoutez au minimum un preload de l’image LCP (si stable) et vérifiez ses dimensions.
<?php
add_action('wp_head', function () {
if (!is_front_page()) {
return;
}
// Remplacez par votre URL réelle (idéalement versionnée/CDN).
$hero = 'https://example.test/wp-content/uploads/2026/04/hero.webp';
echo "<link rel='preload' as='image' href='" . esc_url($hero) . "' fetchpriority='high' />n";
}, 5);
2) JS long (INP/Total Blocking Time)
J’ai souvent vu des sites où le CSS est “correct”, mais le thread principal est bloqué par 400–900 ms de JS (sliders, trackers, widgets). Dans ce cas, votre optimisation doit aussi passer par defer et la suppression de scripts inutiles. (Je reste volontairement centré CSS ici, mais ne l’ignorez pas.)
3) Cache page absent
Sans cache page (ou cache serveur), votre TTFB explose et masque tout gain front. Vérifiez que votre HTML est servi rapidement avant de peaufiner le critical.
Pièges et erreurs courantes
Erreurs que je vois régulièrement
- Copier le code au mauvais endroit : dans
functions.phpdu thème parent (perdu à la prochaine mise à jour). Préférez un plugin ou un thème enfant. - Oublier un point-virgule dans un mu-plugin : écran blanc. Activez les logs et testez sur staging.
- Hook inadapté : tenter d’imprimer le critical dans
wp_enqueue_scriptsau lieu dewp_head. - Différer une feuille critique : menu/header non stylé, CLS, FOUC.
- Conflit de cache : vous modifiez le critical, mais Cloudflare / plugin cache sert l’ancien HTML. Purgez tout (cache page + CDN + navigateur).
- Tester sur production sans sauvegarde : un mauvais filtre
style_loader_tagpeut casser le head. - Snippet d’un vieux tutoriel : patterns obsolètes ou non compatibles avec votre builder. Sur WP 6.9.4, restez sur les APIs actuelles et testez.
Tableau de diagnostic
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| FOUC visible (page non stylée au chargement) | Critical CSS incomplet ou feuille critique différée | Regarder le head : critical contient-il header/nav/hero ? Styles principaux en preload ? | Augmenter la couverture du critical (header/hero), retirer certains handles de la liste “defer” |
| CLS augmente après mise en place | Styles de dimensions (header, images, fonts) chargés trop tard | Lighthouse : “Avoid large layout shifts”, observer les éléments qui bougent | Inclure dimensions critiques dans le critical, fixer width/height, éviter de différer CSS layout |
| Aucun gain Lighthouse | LCP dominé par image/JS/TTFB | Identifier l’élément LCP et la cause (image lourde, JS long, serveur lent) | Preload image LCP, optimiser image, réduire JS, activer cache page |
| CSS non chargé (page cassée) | Filtre style_loader_tag génère un HTML invalide |
View source : balises link fermées, guillemets, attributs | Revenir au HTML original pour les handles critiques, tester handle par handle |
| Le critical ne s’affiche jamais | Mauvais chemin fichier / permissions uploads | Vérifier existence de wp-content/uploads/critical-css/critical-front.css |
Corriger permissions, chemin, ou stocker dans un dossier accessible |
| Ça marche en non connecté, pas en connecté | Barre admin + styles différents + condition is_user_logged_in() |
Comparer HTML head connecté vs invité | Décider : soit optimiser aussi connecté, soit accepter des variantes et tester séparément |
Conseils de maintenance
- Régénérez le critical après : changement de thème, mise à jour majeure du builder, refonte header/hero, ajout d’une police, changement de breakpoints.
- Gardez un budget : critical inline < 15–20 KB (sinon vous alourdissez le HTML et vous perdez le bénéfice).
- Surveillez la dérive : un builder peut doubler le CSS en quelques mois à force d’ajouts de modules.
- Testez mobile d’abord : CPU plus lent, réseau plus fragile, viewport différent (critical différent).
- Versionnez vos assets : si vos CSS ont des query strings cohérentes, le cache navigateur devient enfin efficace.
Ressources
- Hook wp_head (developer.wordpress.org)
- Filtre style_loader_tag (developer.wordpress.org)
- wp_enqueue_style (developer.wordpress.org)
- Query Monitor (wordpress.org)
- Dépôt WordPress core (github.com/WordPress)
- WordPress Core Trac (core.trac.wordpress.org)
- OPcache (php.net)
FAQ
Est-ce que je peux générer le Critical CSS en PHP à chaque page vue ?
Techniquement oui, pratiquement non. L’extraction correcte nécessite un moteur de rendu (headless) et coûte cher. En prod, c’est un risque de perf et de sécurité. Générez offline, mettez en cache, déployez.
Pourquoi limiter le critical à ~20 KB ?
Parce que vous gonflez le HTML, vous ralentissez le téléchargement du document, et vous augmentez le travail de parsing CSS. Le critical doit rester “chirurgical”. Au-delà, vous perdez l’effet “débloquer le rendu”.
Preload + onload, ce n’est pas “sale” ?
C’est un compromis pragmatique que je vois fonctionner sur beaucoup de sites WordPress. Le vrai “propre” serait de scinder le CSS en chunks critiques/non critiques au build, mais avec des builders, c’est rarement réaliste sans outillage lourd.
Dois-je différer le CSS principal du thème ?
Rarement. Si votre thème met la structure (header, containers, typographie) dans le CSS principal, le différer provoque FOUC/CLS. Préférez : inline critical + garder le CSS principal chargé normalement, et différer les “extras”.
Comment gérer plusieurs viewports (mobile + desktop) ?
Deux options : générer un critical mobile (prioritaire) et accepter un rendu desktop légèrement moins optimisé, ou générer deux critical et les conditionner via media queries. Attention : deux critical peuvent doubler le poids inline.
Mon site a une CSP (Content-Security-Policy). L’inline CSS va casser ?
Oui, si votre CSP interdit style-src 'unsafe-inline'. Dans ce cas, vous devez utiliser des nonces/hashes CSP pour autoriser ce bloc inline. C’est faisable, mais ça sort du cadre “copier-coller” et doit être testé avec soin.
Le critical change à chaque modification Elementor/Divi, c’est ingérable ?
Ça devient ingérable si vous le faites à la main. Avec un script headless déclenché en CI (ou après publication), c’est maintenable. Le point clé est d’avoir une liste d’URLs représentatives.
Est-ce compatible avec un plugin de cache ?
Oui, et c’est même recommandé. Mais purgez après mise à jour du critical, sinon vous servez l’ancien HTML qui contient l’ancien inline. Pensez aussi au CDN (Cloudflare) qui peut cacher le HTML.
Pourquoi mon wp_dequeue_style ne marche pas ?
Dans 80% des cas : priorité trop basse (le style est ajouté après), mauvais handle, ou condition qui ne matche pas la page. Listez les handles (snippet ?cssdebug=1) et dequeuez à priorité 100.
Est-ce que WordPress 6.9.4 a une API native “Critical CSS” ?
Pas une API “one click” qui extrait et inline automatiquement votre CSS above-the-fold. WordPress fournit les hooks et les APIs d’enqueue ; la stratégie critical reste une responsabilité thème/projet, surtout avec des builders.
Je peux juste installer un plugin “Critical CSS” et oublier ?
Parfois ça marche, souvent ça dérive. Les plugins génériques ne connaissent pas vos templates, vos A/B tests, vos variations builder. Si vous voulez du fiable, gardez la génération offline + un plugin minimal d’inlining comme ci-dessus.