Saltar al contenido

render por dentro: la raíz de la app

render no es magia: abre un createRoot, guarda su dispose, inserta el árbol en el DOM y te devuelve una función que lo destruye y vacía el contenedor. Toda la aplicación cuelga de esa única raíz. hydrate recorre el mismo camino pero adopta el DOM del servidor en vez de crearlo, activando el contexto de hidratación.

render es createRoot con inserción

Desnudado de detalles, render cabe en pocas líneas. Abre una raíz, se queda con su dispose, e inserta en el contenedor lo que produce tu componente. La función que devuelve dispone la raíz y, además, vacía el nodo del DOM.

TSX
// Version simplificada del render real de solid-js/webfunction render(code, element, options = {}) {  let disposer;  createRoot((dispose) => {    disposer = dispose;    insert(element, code()); // ejecuta el componente e inserta su salida  }, options.owner);         // owner opcional para heredar contexto  return () => {    disposer();              // destruye toda la reactividad de la app    element.textContent = ""; // y limpia el DOM del contenedor  };}

Tres gestos, ni uno más: createRoot abre el owner de la app, insert ejecuta tu componente y clava su salida en el contenedor, y la función devuelta encadena el dispose de la raíz con el vaciado del nodo. Todo lo que asocias con «arrancar una aplicación Solid» cabe en esas tres operaciones; el resto son detalles de hidratación y opciones.

Así lo usas todos los días, aunque quizá nunca guardaste el retorno:

TSX
import { render } from "solid-js/web";const dispose = render(() => <App />, document.getElementById("root")!);// dispose() desmonta la aplicacion entera cuando quieras.

El componente App corre dentro de la raíz. Cada componente que monte crea un owner hijo; cada efecto, cada memo, cada onCleanup se registran en algún punto de ese subárbol. La raíz de render es el ancestro común de absolutamente todo.

Dos matices que rara vez se explican. Primero, render ejecuta tu componente de forma ávida, dentro del createRoot, así que al volver de la llamada la interfaz ya está insertada en el DOM; no hay un pase diferido que esperar. Segundo, render admite un objeto de opciones con un campo owner que se reenvía como el owner del que la raíz hereda contexto —el mismo mecanismo del nivel anterior—. Sirve para montar un árbol secundario que debe ver los proveedores de otro: un overlay pintado en un contenedor aparte pero que necesita el contexto de la app principal.

TSX
// Montar un arbol secundario que hereda el contexto de un owner dado.render(() => <Overlay />, contenedorAparte, { owner: getOwner()! });

La función que render devuelve compone dos limpiezas en una: el dispose de la raíz, que apaga toda la reactividad, y un element.textContent = "" que retira el DOM del contenedor. Por eso desmontar una app con ella deja el nodo host vacío y reutilizable, sin efectos huérfanos ni nodos zombis. Es el cierre completo —reactividad y DOM— en una sola llamada.

Una sola raíz para todo el árbol

Tu aplicación no es una colección de reactividades sueltas: es un solo árbol de owners cuya cima es la raíz de render. Esa unicidad es la que hace que desmontar sea trivial —un dispose en la cima cae en cascada hasta la última hoja— y la que garantiza que las limpiezas corran en orden, hijos antes que padres.

El dispose que render devuelve es, por eso, el interruptor general. En una SPA rara vez lo usas —la app vive tanto como la pestaña—, pero es esencial en tres sitios: al desmontar un micro-frontend incrustado en otra aplicación, al limpiar entre casos en tests de integración, y al reemplazar la app en escenarios de recarga total. Guardar ese retorno cuesta una variable y te da el poder de apagar limpiamente.

Que el árbol sea uno solo explica además el determinismo del cierre. Al invocar el dispose de la cima, Solid recorre el subárbol entero de hojas a raíz: primero mueren los efectos de las filas más profundas, luego sus contenedores, luego los layouts y por último App. Ningún onCleanup corre mientras algo de lo que dependía sigue vivo. No es que desmontar sea rápido; es que es ordenado, y ese orden es una propiedad de la estructura de árbol, no de tu cuidado al escribir las limpiezas.

Este mismo mecanismo es el que un router explota. Cada cambio de ruta dispone el owner de la vista saliente y abre uno nuevo para la entrante, todo dentro de la única raíz de la app y sin tocar jamás la cima. Cuando entiendes render como un createRoot, ves que navegar en una SPA no es más que disponer y crear subárboles bajo esa raíz común: la app permanece; sus vistas nacen y mueren colgadas de ella.

hydrate: la misma raíz sobre DOM existente

En SSR el servidor ya mandó HTML pintado. En el cliente no quieres recrear ese DOM, sino adoptarlo: reconectar la reactividad a los nodos que ya están. Eso hace hydrate. Por dentro recorre el mismo camino que render —abre un createRoot, ejecuta tu componente—, pero antes activa el contexto de hidratación: una configuración compartida que le dice al runtime que, en lugar de crear elementos nuevos, camine el DOM existente y le enganche los efectos.

TSX
import { hydrate } from "solid-js/web";// El servidor sirvio el HTML; el cliente lo adopta sin recrearlo.hydrate(() => <App />, document.getElementById("root")!);

La semántica de disposición es idéntica: hydrate también devuelve una función que destruye la raíz. Lo único distinto es el arranque —adoptar en vez de crear— y el contexto de hidratación que se instala durante ese primer pase y se retira al terminar.

Visto por dentro, hydrate es un envoltorio fino: prepara ese contexto de hidratación en una configuración compartida y luego delega en el mismo render —y por tanto en el mismo createRoot— indicándole que reutilice los nodos existentes en lugar de fabricar otros.

TSX
// Esquema de hydrate: contexto de hidratacion mas el render de siempre.function hydrate(code, element, options = {}) {  activarContextoDeHidratacion(options); // ids, eventos diferidos, registro  const dispose = render(code, element, {    ...options,    nodosExistentes: [...element.childNodes], // adoptar, no crear  });  desactivarContextoDeHidratacion();  return dispose;}

La lección es que hidratar no es una tercera vía junto a renderizar y montar a mano: es renderizar con una instrucción extra —«reutiliza lo que ya hay»— sobre la raíz de siempre. Del lado del servidor, renderToString produce el HTML de una pieza, o renderToStream lo envía por trozos a medida que el árbol resuelve su parte asíncrona; ambos son el trabajo espejo que hydrate revive en el cliente. Emparejar bien las dos mitades —qué se pinta en el servidor y qué se adopta en el cliente— es toda la disciplina del SSR, y descansa sobre esta única raíz compartida por ambos lados.

Un apunte de actualidad: en 2026 conviven con la hidratación clásica esquemas más finos —islas, hidratación parcial y perezosa— que recortan cuánto JavaScript revive y cuándo. Cambian qué porciones del árbol se hidratan y en qué momento, pero no el modelo de fondo: cada isla que despierta es, de nuevo, una raíz reactiva que adopta su fragmento de DOM y que, en principio, podría disponerse. La primitiva no cambia; cambia la granularidad con que la aplicas.

Dos árboles independientes, cada uno con su propio interruptor:

TSX
const cerrarApp = render(() => <App />, document.getElementById("root")!);const cerrarChat = render(() => <Widget />, document.getElementById("chat")!);// Se apagan por separado; no comparten owner ni ciclo de vida.cerrarChat(); // el widget desaparece; la app sigue intacta

Esto es lo que hace de Solid un buen ciudadano dentro de páginas que no son suyas: un widget de chat, un panel de comentarios o un configurador incrustado en un sitio heredado son, cada uno, un render con su raíz y su dispose. El anfitrión no necesita saber nada de Solid; le basta con dar un nodo y, si acaso, llamar al dispose cuando retire esa sección de la página.

Que cada montaje traiga su propio dispose es justo lo que evita que estas incrustaciones se conviertan en fugas dentro de aplicaciones ajenas. En una página de larga vida —un panel de administración, un CMS— montar y no desmontar widgets acaba apilando árboles reactivos muertos; guardar el dispose de cada render y llamarlo al quitar la sección mantiene la memoria plana. El interruptor que casi nunca usas en una SPA propia se vuelve imprescindible en cuanto vives de invitado en la casa de otro.