11.1 — Pourquoi le JS natif casse en React/SSR
⏱️ TL;DR — Le JS de Bootstrap manipule le DOM directement (classes, attributs, écouteurs attachés au chargement). React contrôle le DOM via son virtual DOM ; en SSR (Next.js), le HTML est d’abord rendu côté serveur où
document/windown’existent pas. Résultat : data-attributes non initialisés,document is not defined, désynchronisation état/DOM. La solution :react-bootstrapou une initialisation JS soigneuse dansuseEffect.
🎯 Objectifs
- Comprendre les trois sources de conflit.
- Éviter les erreurs
document is not defined. - Savoir quand
useEffectsuffit.
Les trois conflits
- DOM impératif vs virtual DOM : Bootstrap ajoute/retire des classes (
show,collapse) que React ne « connaît » pas — au prochain re-render, React peut écraser ces changements. - Initialisation au chargement : la Data API attache ses écouteurs une fois, au
DOMContentLoaded. Les composants React montés après ne sont pas pris en compte. - SSR : côté serveur,
import 'bootstrap'qui touchewindow/documentlèvedocument is not defined.
L’erreur classique en Next.js
// ❌ dans un composant serveur (App Router) : plante au build
import 'bootstrap/dist/js/bootstrap.bundle' // touche windowInitialisation correcte dans un composant client
'use client'
import { useEffect, useRef } from 'react'
export function InfoTooltip({ title, children }) {
const ref = useRef(null)
useEffect(() => {
let instance
// import dynamique : chargé seulement côté client
import('bootstrap/js/dist/tooltip').then(({ default: Tooltip }) => {
instance = new Tooltip(ref.current, { title })
})
return () => instance?.dispose() // cleanup obligatoire
}, [title])
return <span ref={ref} data-bs-toggle="tooltip">{children}</span>
}Points clés : 'use client', import dynamique (évite le SSR), instanciation dans useEffect, dispose() au cleanup.
Pourquoi c’est fragile
Même bien fait, vous re-synchronisez à la main l’état React et le DOM Bootstrap. Pour un dropdown ou une modale (focus trap, fermeture au clic extérieur), c’est beaucoup de code fragile — d’où l’existence de react-bootstrap.
⚠️ Piège — Oublier le
dispose()au cleanup crée des fuites mémoire et des instances fantômes quand le composant se démonte/remonte (navigation SPA). Toujours nettoyer dans lereturnduuseEffect.
💡 Pour un dev React — Règle simple : CSS de Bootstrap = OK partout ; JS de Bootstrap = seulement en composant client, avec import dynamique + dispose, ou mieux,
react-bootstrap. Ne jamais importer le JS dans un composant serveur.
✏️ Exercices
- Pourquoi
import 'bootstrap'plante-t-il dans un composant serveur Next.js ?
✅ Solution
Le JS touche window/document, absents côté serveur → document is not defined.
- Que faut-il faire au démontage d’un composant qui a instancié un tooltip ?
✅ Solution
Appeler .dispose() dans le cleanup du useEffect.
🧠 Quiz de révision
1. Pourquoi le JS Bootstrap entre-t-il en conflit avec React ?
Il manipule le DOM directement, alors que React le contrôle via le virtual DOM.
2. Quelle erreur typique en SSR ?
document is not defined.
3. Où initialiser un composant JS Bootstrap en React ?
Dans un useEffect d’un composant 'use client', avec import dynamique.
4. Que faire au cleanup ?
.dispose() l’instance.
5. Le CSS de Bootstrap pose-t-il problème en SSR ?
Non — seul le JS pose problème.
Chapitre suivant : 11.2 — react-bootstrap