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.
Llamas a render en la primera línea de tu aplicación y algo aparece en pantalla. Parece el borde del framework, el punto donde acaba lo que puedes entender y empieza la maquinaria. No lo es. render es una función corta y legible que hace tres cosas: abre un createRoot, mete el árbol que devuelve tu componente dentro de un contenedor del DOM, y te entrega un dispose para desmontarlo todo. Entender que la raíz de tu aplicación es literalmente el createRoot del nivel anterior cierra el círculo: no hay una primitiva especial para la app, solo la misma raíz que ya sabes abrir, colocada en la cima.
- Ver que
renderse construye sobrecreateRooty devuelve una función de disposición. - Entender que toda la aplicación es un único árbol de owners con una sola raíz.
- Desmontar una app entera invocando el
disposequerenderdevuelve. - Situar
hydratecomo el mismo camino que adopta el DOM del servidor.
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.
// Version simplificada del render real de solid-js/web
function 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:
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.
// 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.
flowchart TD R[render abre createRoot: raiz de la app] --> A[Owner de App] A --> H[Owner de Header] A --> M[Owner de Main] M --> L[Owner de Lista] L --> E[Efectos y memos de cada fila] D[dispose de render] --> R style R fill:#a6e3a1,color:#11111b style D fill:#f38ba8,color:#11111b
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.
Cada render toma el contenedor como suyo y, al disponer, lo vacía con element.textContent = "". Llamar a render dos veces sobre el mismo nodo sin disponer el primero deja dos árboles pisándose, y disponer uno puede borrar el DOM del otro. Un contenedor, una raíz viva: si necesitas reemplazar el árbol, dispón el anterior antes de montar el nuevo; si necesitas dos árboles, dales dos contenedores.
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.
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.
// 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.
Nada obliga a una sola llamada a render. Puedes montar árboles independientes en contenedores distintos —un widget en una página que no es de Solid, un panel incrustado— y cada render abre su propia raíz con su propio dispose. Son árboles de owners paralelos, sin ancestro común, que no comparten ciclo de vida: dispones cada uno por separado. Para árboles secundarios que sí deban vivir dentro de la reactividad de tu app —un portal, un overlay imperativo— usa createRoot a mano y cose su dispose con onCleanup, en lugar de un segundo render desconectado.
Dos árboles independientes, cada uno con su propio interruptor:
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.
Durante todo el aprendizaje, render fue un conjuro: la puerta por la que Solid entraba en la página, opaca a propósito para que empezaras a construir sin mirar debajo. Verla desde createRoot la vuelve transparente y, con ella, toda la arquitectura de una aplicación Solid. No hay dos clases de reactividad, una «de componentes» y otra «manual»: hay una sola primitiva de ownership, y tu aplicación entera es su instancia más grande, abierta en la primera línea y viva mientras la pestaña respire. El componente que montas no es la raíz; es el primer hijo de la raíz. Los efectos que escribes no flotan; cuelgan de un árbol cuya cima abrió render por ti. Y el dispose que casi nunca guardas es el mismo que devuelve cualquier createRoot: el interruptor que apaga el árbol entero de abajo hacia arriba. Esta continuidad es la recompensa conceptual del nivel. Cuando entiendes que la app es un createRoot, entiendes que montar y desmontar una aplicación, montar y desmontar un componente, y abrir y cerrar un scope en un test son el mismo hecho a tres escalas. La hidratación no rompe esa unidad: solo cambia el arranque, adoptando el DOM que el servidor ya pintó en vez de crearlo, y deja intacto el resto del modelo. Dominar render no es memorizar su firma; es reconocer que ya lo dominabas al dominar la raíz.
- Guarda el retorno de
renderen una variabledisposey, tras unos segundos consetTimeout, invócalo; observa que la app desaparece y su DOM queda vacío. - Escribe la versión simplificada de
rendersobrecreateRooteinsert, y explica qué añade a la raíz desnuda. - Monta dos
renderen dos contenedores distintos y comprueba que cadadisposeafecta solo a su árbol. - Razona por qué desmontar la app es un solo
disposeen la cima y no requiere recorrer los componentes a mano. - Explica en qué se diferencia
hydratederenderen el arranque y por qué su disposición es idéntica.