Tu inspectes un composant Next, tu vois aspect-[12/5], z-[100] ou h-[80vh] dans le DOM, et pourtant visuellement… rien. Pas de style. Pas de règle. Juste ton HTML qui te nargue.
Ce bug est vicieux parce qu’il ressemble à une erreur classique de Tailwind (mauvais content, cache, classe dynamique, etc.). Sauf que là, le problème peut venir de la combo Next.js + Turbopack qui “rate” certaines classes à valeur arbitraire sous Tailwind v4. Résultat : le DOM est correct, mais le CSS correspondant n’est jamais généré/injecté.
Le symptôme typique : la classe est là, mais la règle CSS n’existe pas
Le pattern est assez reconnaissable. Les classes “normales” passent (genre flex, p-4, bg-black), mais dès que tu touches aux valeurs arbitraires entre crochets, ça casse. Et quand je dis “ça casse”, ce n’est pas “ça s’applique mal”, c’est il n’y a rien à appliquer.
Le piège : côté DevTools, tu peux passer 20 minutes à regarder l’onglet “Styles” en te demandant quelle règle écrase quoi. Alors que la vérité est plus bête : la règle n’a jamais été produite. Donc forcément, elle ne peut ni s’appliquer, ni être surchargée.
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€ !
Repro minimal (pour arrêter de douter de ton code)
Quand tu es dans le flou, la première étape c’est de sortir ton composant de son contexte. Pas besoin de ton design system, pas besoin de tes providers, pas besoin de tes 12 layouts imbriqués. Juste une page avec trois classes arbitraires.
// app/page.tsx
export default function Page() {
return (
<main className="p-8">
<div className="aspect-[12/5] h-[80vh] z-[100] bg-black text-white">
Test
</div>
</main>
)
}
Si tu vois bien le bloc noir (donc bg-black OK) mais que l’aspect ratio, la hauteur et le z-index ne changent pas, tu es probablement dans le cas décrit par l’issue.
Comment prouver en 2 minutes que c’est la génération CSS qui foire
Je te conseille une preuve simple, qui coupe court aux débats internes du style « c’est la faute à React », « c’est le layout », « c’est Tailwind qui purge trop agressivement ».
D’abord, vérifie côté DevTools si une règle existe. Clique ton élément, cherche aspect-ratio, height ou z-index dans l’onglet Styles. Si tu ne vois aucune règle venant de Tailwind pour ces classes, ce n’est pas un problème de cascade. C’est un problème d’émission.
Ensuite, cherche carrément dans le CSS généré. Dans Next, tu peux ouvrir les assets CSS (souvent dans /_next/static/...) et faire une recherche texte sur un fragment distinctif. Par exemple 80vh ou z-index: 100 ou aspect-ratio. Si tu ne trouves rien, tu as ton diagnostic : la chaîne de build ne sort pas les règles liées aux crochets.
Pourquoi ce bug est si trompeur avec Tailwind (et pourquoi tu pars dans la mauvaise direction)
Quand Tailwind “oublie” des styles, on pense tout de suite à la config content, aux classes construites dynamiquement, ou à un dossier non scanné. Et c’est logique : historiquement, c’est souvent ça.
Sauf qu’ici, tu peux avoir un code 100% statique, un chemin de scan nickel, et le souci reste. En gros, tu fais tout bien… mais le compilateur côté Next/Turbopack n’attrape pas correctement certains patterns, et tu te retrouves avec des classes visibles dans le markup mais “inconnues” côté CSS.
Mitigations qui tiennent vraiment en prod (sans réécrire ton UI)
Je vais être honnête : quand tu es sous pression (release, refonte, migration), tu ne veux pas “comprendre la poésie” du tooling. Tu veux une interface correcte, maintenant, et un plan pour stabiliser après.
La mitigation la plus brute, mais souvent la plus fiable, c’est de sortir les valeurs arbitraires critiques en inline style le temps que l’outil redevienne stable. C’est moche, mais c’est prédictible. Typiquement, un style={{ aspectRatio: "12 / 5" }} ou style={{ height: "80vh" }} sur 2-3 composants stratégiques peut te sauver une mise en prod.
Deuxième option, plus “Tailwind-friendly” : éviter les crochets en créant des tokens de thème. Quand tu remplaces une classe arbitraire par une classe nommée, tu réduis énormément les risques de parsing foireux. Si ton design a vraiment besoin d’un z-index à 100, tu peux l’assumer dans le thème plutôt que de le taper en dur partout.
// tailwind.config.js (exemple)
export default {
theme: {
extend: {
zIndex: {
100: "100",
},
height: {
"80vh": "80vh",
},
aspectRatio: {
"12/5": "12 / 5",
},
},
},
}
Ça te permet d’écrire z-100, h-80vh, aspect-12/5 au lieu de z-[100] etc. C’est plus verbeux côté config, mais en échange tu récupères un build plus robuste et un code plus cohérent. Et ça évite l’effet “au secours, on a 43 valeurs arbitraires différentes pour la même intention”.
Troisième levier, quand tu as le choix : désactiver Turbopack pour le build concerné (ou repasser sur un mode non Turbo selon ton contexte). Ce n’est pas “fun”, mais sur une UI qui doit sortir, je préfère un bundler un peu moins sexy qui respecte mon CSS, plutôt qu’un outil rapide qui me laisse des composants à poil.
Et la « safelist » dans tout ça ? Utile parfois, mais ce n’est pas magique
Quand Tailwind manque des classes, le réflexe « safelist » est naturel. Sauf que dans ce scénario précis, le souci n’est pas forcément que Tailwind “purge trop”. Le souci peut être que le pipeline ne comprend pas (ou ne transmet pas) correctement ces classes, donc même en forçant, tu peux te retrouver avec des résultats incohérents.
Mon avis terrain : la safelist est un bon outil quand tu as des classes réellement dynamiques (CMS, mapping, contenu user), mais pour des valeurs arbitraires “hardcodées” dans ton JSX, si elles disparaissent, je considère que c’est un signal d’instabilité du toolchain. Et je préfère corriger par inline style temporaire, ou par tokens de thème, ou par changement de mode de build, plutôt que d’empiler du scotch dans la config.
Ce que je vérifie avant de toucher à quoi que ce soit (anti-perte de temps)
Oui, on parle d’un bug outillage. Mais je vérifie quand même deux ou trois évidences, parce que j’ai déjà vu des équipes partir en croisade contre Next alors qu’elles avaient juste un import CSS cassé.
Je m’assure que le fichier CSS global avec @import "tailwindcss"; (ou l’entrée Tailwind équivalente dans v4) est bien chargé, qu’il n’est pas conditionné à un layout, et que le hot reload ne m’affiche pas un état fantôme. Ensuite, je fais un test idiot : je mets une classe Tailwind basique que je suis sûr de voir tout de suite (un bg-red-500 ou un text-3xl). Si ça marche, Tailwind est “vivant”. Si seules les classes arbitraires merdent, je ne perds pas plus de 5 minutes sur le reste.
Checklist QA avant un bump Next/Tailwind (la version qui évite les surprises)
Si tu es lead front ou juste la personne qui fait les upgrades, garde une règle simple : teste la génération, pas juste le rendu “global”. Sur un bump Next ou Tailwind, je veux toujours un petit écran de test qui couvre des classes à risque. Les valeurs arbitraires en font partie, au même titre que les variants un peu tordus, les data-[state=...], ou certaines combinaisons responsive.
Et surtout, ne te contente pas de « ça a l’air ok ». Ouvre le CSS généré et cherche une règle que tu sais distinctive. Le jour où ça disparaît, tu le vois immédiatement, avant d’avoir une régression sournoise qui remonte via un screenshot Slack trois jours plus tard.
Conclusion : quand le DOM ment, regarde le CSS généré
Ce genre de bug te fait douter de toi, parce que visuellement tu as l’impression d’avoir “raté un truc”. Alors que non : tu peux être dans un cas où la classe est bien rendue côté React, mais le CSS n’est pas produit à cause de la toolchain (ici, un souci rapporté entre Tailwind v4 et Turbopack).
Mon approche, simple et efficace : je prouve d’abord l’absence de règle dans le CSS, puis je mitige proprement (inline style ciblé ou tokens de thème), et je ne reviens à l’optimisation que quand le combo de versions est stable. Parce qu’une UI qui s’affiche mal, ce n’est pas un détail technique. C’est un bug produit.