Hydration: el HTML que cobra vida
¿Por qué React se queja de que el servidor y el cliente no coinciden?
Al terminar vas a poder explicar
- Explicar en qué se diferencia hidratar de montar
- Enumerar las causas reales de un error de hydration
- Arreglar una diferencia de hydration sin apagar el renderizado en servidor
En el montaje normal, React crea los nodos del host desde cero. En la hydration hace algo distinto: los nodos ya están en el documento, puestos por el servidor, y React tiene que adoptarlos.
Adoptar en vez de crear
React construye el árbol de Fibers normalmente, pero en lugar de llamar a createElement en completeWork, va emparejando cada Fiber con el nodo que le corresponde y le engancha los listeners.
1createRoot(nodo).render(<App />)2 → completeWork crea document.createElement("div")3 → el commit inserta los nodos4 5hydrateRoot(nodo, <App />)6 → completeWork ADOPTA el <div> que ya está en el documento7 → el commit sólo engancha los manejadores de eventosPor eso el HTML del servidor aparece al instante — es HTML común — pero no responde a nada hasta que la hydration termina. Ese hueco entre «se ve» y «funciona» es real y se mide.
Por qué React exige coincidencia exacta
React espera que lo que renderiza en el cliente sea idéntico al HTML del servidor. Si no coincide, no puede saber cuál de los dos está bien — así que descarta el HTML del servidor y renderiza todo del lado del cliente. Es lento y se nota.
Las cuatro causas reales
- Valores que cambian entre ejecuciones.
Date.now(),Math.random(),new Date(). El servidor renderiza uno, el cliente otro. - APIs del navegador durante el render.
window,localStorage,navigatorno existen en el servidor. - HTML inválido. Un
<div>adentro de un<p>: el parser del navegador lo reacomoda y el árbol deja de coincidir. Esta es la más difícil de encontrar. - Extensiones del navegador que inyectan nodos antes de que React hidrate.
Las tres soluciones
1// 1. Renderizar en dos pasos: el servidor y el primer render coinciden.2function Reloj() {3 const [montado, setMontado] = useState(false);4 useEffect(() => setMontado(true), []);5 6 if (!montado) return <span>--:--</span>; // igual en servidor y cliente7 return <span>{new Date().toLocaleTimeString()}</span>;8}9 10// 2. Decirle a React que ignore ESTE nodo (y sólo su contenido de texto).11<time suppressHydrationWarning>{new Date().toISOString()}</time>12 13// 3. Sacarlo del servidor por completo, si de verdad sólo tiene sentido14// en el navegador.15const Mapa = dynamic(() => import("./Mapa"), { ssr: false });useId existe por esto
Generar ids únicos con un contador rompe la hydration: el servidor y el cliente arrancan sus contadores por separado y producen ids distintos.
useId deriva el identificador de la posición del componente en el árbol. Como el árbol es el mismo de los dos lados, el id también. Ese es todo el problema que resuelve — y por eso nunca sirve como key de una lista.
Antes de seguir · predecí
Un componente muestra «Buenos días» o «Buenas noches» según la hora. En producción tira error de hydration a veces sí y a veces no. ¿Por qué es intermitente?
Lo que te llevás
Hidratar es adoptar HTML existente en vez de crearlo. React exige coincidencia exacta porque no tiene forma de saber cuál de las dos versiones está bien. Las causas son siempre las mismas: valores que cambian, APIs del navegador durante el render, HTML inválido o extensiones. Y useId existe exactamente para no romperla.
Antes de marcarla, comprobá
- ¿Podés explicar explicar en qué se diferencia hidratar de montar?
- ¿Podés explicar enumerar las causas reales de un error de hydration?
- ¿Podés explicar arreglar una diferencia de hydration sin apagar el renderizado en servidor?