Los entry points: cliente y servidor
Qué hacen entry-client y entry-server y cuándo tocarlos. entry-client arranca la aplicación en el navegador con mount y StartClient, hidratando el HTML que llegó del servidor. entry-server define el documento HTML completo con createHandler y StartServer, incluyendo el head, el div contenedor y los scripts de hidratación. El apretón de manos entre ambos es el id del contenedor, que debe coincidir. Por qué en el flujo normal apenas se tocan, y cuáles son las razones legítimas para editarlos: etiquetas de head, idioma, arranque solo de cliente.
Los dos entry points son los ficheros que más miedo dan y menos se tocan. Su aire de plantilla intocable esconde una idea simple: como tu aplicación vive en dos entornos, cada uno necesita su propio arranque. entry-server fabrica el documento HTML desde la nada en el servidor; entry-client se engancha a ese documento ya recibido y lo reanima en el navegador. Entre ambos hay un apretón de manos —el id del contenedor— que, si se rompe, hace fallar la hidratación entera. Esta lección abre las dos cajas, explica línea por línea qué hace cada una y, sobre todo, fija la regla de oro: cuándo tienes una razón legítima para editarlas y cuándo lo que buscas pertenece en realidad a app.tsx.
- Explicar por qué existen dos entry points y qué entorno arranca cada uno.
- Leer
entry-clienty entender quemounthidrata o renderiza según el modo. - Leer
entry-servery reconocer el documento, el contenedor y los scripts. - Decidir con criterio cuándo tocar un entry point y cuándo ir a
app.tsx.
Dos entradas para un mismo árbol
Recuerda el modelo del nivel: hay un solo árbol de aplicación, app.tsx, y dos entornos que lo ejecutan. Un entry point es el código de arranque específico de un entorno: lo que corre antes de que el árbol exista, para traerlo a la vida en ese lugar. En el servidor, arrancar significa renderizar el árbol a una cadena de HTML y envolverla en una página completa; en el cliente, significa tomar esa página ya pintada y volverla interactiva. Misma aplicación, dos maneras de encenderla, dos ficheros.
La palabra clave es antes. Todo lo que escribes en un entry point sucede en el umbral, en el instante previo a que tu aplicación exista como árbol reactivo. Por eso su contenido es tan escaso y tan peculiar: no es lógica de negocio ni interfaz, es la ceremonia de encendido de un entorno. Confundir ese umbral con el interior de la aplicación es el origen de casi todos los tropiezos que veremos al final.
flowchart TD ES[entry-server renderiza app.tsx a HTML] --> DOC[documento con div id app y scripts] DOC -->|viaja al navegador| CLI[el navegador pinta el HTML al instante] CLI --> EC[entry-client monta StartClient en el div id app] EC -->|hidrata| LIVE[la misma app ahora interactiva] style ES fill:#f9e2af,color:#11111b style EC fill:#a6e3a1,color:#11111b style LIVE fill:#89b4fa,color:#11111b
entry-client: reanimar en el navegador
El entry de cliente es minúsculo, y su brevedad es una pista de lo poco que tienes que tocarlo. Importa mount y StartClient y monta la aplicación sobre un nodo del DOM.
// src/entry-client.tsx
import { mount, StartClient } from "@solidjs/start/client";
mount(() => <StartClient />, document.getElementById("app")!);
StartClient es el componente que conecta el enrutador del lado del cliente con tu app.tsx; no lo escribes tú, lo aporta SolidStart. mount es la operación de arranque, y su comportamiento es adaptativo: cuando hay SSR, el nodo app ya contiene el HTML que envió el servidor, así que mount hidrata —adopta ese DOM existente y le engancha la reactividad sin volver a pintarlo—; cuando el proyecto es SPA, el nodo está vacío y mount renderiza desde cero. Una misma línea sirve a los dos modos porque delega en el runtime la decisión de hidratar o crear.
Lo esencial es el segundo argumento: document.getElementById("app"). Ese identificador tiene que coincidir con el del contenedor que define el servidor. Es la mitad cliente del apretón de manos.
Que mount decida entre hidratar y renderizar según lo que encuentre en el nodo tiene una consecuencia práctica valiosa: el mismo entry-client sirve sin cambios tanto a un proyecto SSR como a uno SPA. No hay una versión del arranque para cada modo; hay un arranque que se adapta. Es otra cara del isomorfismo de Solid, y la razón de que cambiar el modo del proyecto no te obligue a reescribir las puertas de entrada.
entry-server: fabricar el documento
El entry de servidor tiene más cuerpo porque su trabajo es mayor: en el servidor no existe ninguna página todavía, así que hay que construir el documento HTML entero alrededor de tu aplicación.
// src/entry-server.tsx
import { createHandler, StartServer } from "@solidjs/start/server";
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));
createHandler produce el manejador de peticiones que Nitro invoca por cada solicitud entrante; es el punto donde el mundo de Solid se conecta con el servidor de la capa de abajo. StartServer renderiza tu app.tsx a HTML para esa petición. Y la prop document es la joya: es el único lugar donde tú posees las etiquetas <html>, <head> y <body>. Recibe tres piezas que debes colocar en su sitio:
assets: las etiquetas de estilo, precargas y cabezera que el build inyecta; van en el<head>.children: el HTML de tu aplicación ya renderizada; va dentro del contenedor.scripts: los scripts de hidratación que despertarán la página en el cliente; van al final del<body>.
Aquí está la mitad servidor del apretón de manos: <div id="app"> tiene que llevar el mismo id que busca entry-client. Ese contenedor es a la vez el destino donde el servidor deposita el HTML de la app y el ancla donde el cliente se engancha para hidratar. Si los dos identificadores no coinciden, el cliente no encuentra el DOM que debía adoptar y la hidratación se rompe.
Conviene ver createHandler como lo que es: el punto donde el render de Solid se conecta con Nitro y, por tanto, donde reside por defecto el streaming del SSR que estudiarás en la última lección. No hay que configurarlo para que transmita en trozos —es el comportamiento de fábrica—, pero saber que el modo de render se decide a la altura de este handler te ahorra buscarlo en app.tsx o en app.config.ts, donde no vive. Es un ejemplo más de la geografía del framework: cada decisión tiene un domicilio, y acertar con él es la mitad del oficio.
El id del <div> en entry-server y el argumento de getElementById en entry-client son las dos caras de un mismo contrato. Si editas uno, edita el otro. Un desajuste no siempre da un error ruidoso: a veces produce una página que se ve bien pero no reacciona, porque el servidor pintó el HTML en un sitio y el cliente intentó hidratar en otro que estaba vacío. Es de esos fallos que se diagnostican rápido si conoces el contrato y se persiguen durante horas si no.
Cuándo tocarlos, y cuándo no
La regla de oro es que en el flujo normal no se tocan: son arranque de entorno, no el árbol de tu aplicación. Casi todo lo que un principiante intenta configurar aquí pertenece a app.tsx —los proveedores de contexto, el layout, el enrutador, las fronteras de carga y error— porque eso es la aplicación, y la aplicación es compartida. Meterlo en un entry point lo ataría a un solo entorno y rompería la simetría de la que vive el framework.
Las razones legítimas para editarlos son acotadas y casi todas viven en entry-server, por ser el dueño del documento:
Head global
Fijar el idioma en <html lang>, meter fuentes, un tema de color inicial, etiquetas meta base o un nonce de seguridad. Son propiedades del documento, no de una ruta.
Contexto de peticion
Leer datos de la solicitud con getRequestEvent para influir en el documento, como servir un idioma distinto según cabeceras. Vive en el servidor por naturaleza.
Arranque de cliente
En entry-client, un polyfill, registrar un service worker o inicializar telemetria antes del mount. Cosas que solo tienen sentido en el navegador.
Una edición legítima del entry de cliente se ve así: preparar el entorno del navegador justo antes de encender el árbol.
// entry-client.tsx: una razon legitima, preparar el navegador antes de montar
import { mount, StartClient } from "@solidjs/start/client";
registrarServiceWorker(); // solo tiene sentido en el navegador
mount(() => <StartClient />, document.getElementById("app")!);
Fíjate en el patrón: se toca un entry point cuando lo que quieres es intrínseco a ese entorno concreto —el documento en el servidor, el arranque del navegador en el cliente—. Todo lo demás, por definición, es de la aplicación, y la aplicación vive en app.tsx. Ese criterio es más útil que cualquier lista, porque decide por ti incluso los casos que ninguna lista previó.
Un contraste afila la regla. Un proveedor de tema parece candidato a ir en un entry point porque suena a algo global y de arranque; pero un tema es estado de la aplicación, se comparte entre ambos entornos y debe existir en cada ruta, así que su sitio es la prop root del Router en app.tsx. En cambio, registrar un service worker no es estado de nada: es una gestión del navegador que no tiene análogo en el servidor y que debe ocurrir una sola vez al arrancar el cliente. El primero es aplicación; el segundo, arranque de entorno. Aplicar esa pregunta —si es de la app o del encendido de un entorno— resuelve el noventa por ciento de las dudas sobre dónde colocar algo.
La estadística juega a favor de app.tsx: los entry points son los ficheros que menos veces tocarás en toda la vida de un proyecto, mientras que la raíz y las rutas son donde ocurre casi todo. Cuando te descubras a punto de añadir lógica a un entry point, detente y pregúntate si de verdad es arranque de un entorno concreto o si es la aplicación disfrazada de arranque. Nueve de cada diez veces es lo segundo, y su domicilio es la raíz compartida, no la puerta de un entorno.
La razón profunda de que existan dos entry points, y de que sean tan distintos entre sí, es que renderizar en el servidor y en el cliente son operaciones fundamentalmente asimétricas, aunque partan del mismo árbol. En el servidor no hay nada: cada petición empieza en el vacío, y por eso entry-server tiene que fabricar el documento HTML completo, decidir la estructura del head, colocar el contenedor y sembrar los scripts que más tarde despertarán la página. Es un acto de creación desde cero, repetido en cada solicitud. En el cliente, en cambio, cuando hay SSR ya existe una página entera pintada y visible, y la tarea de entry-client no es crear sino adoptar: enganchar la reactividad al DOM que ya está, un proceso que Solid llama hidratación y que es más barato y más delicado que un render limpio, porque tiene que casar exactamente con lo que el servidor produjo. Esa asimetría —creación contra adopción— explica de un golpe todo lo que rodea a los entry points: por qué el de servidor es más grande y posee el documento, por qué el de cliente es una sola línea, por qué el id del contenedor es un contrato sagrado que ambos deben respetar, y por qué la inmensa mayoría de tu código no vive en ninguno de los dos sino en el árbol compartido que los dos ejecutan. El error que este entendimiento previene es el más común del nivel: tratar los entry points como el panel de control de la aplicación y llenarlos de lógica que pertenece a app.tsx. Los entry points no son donde configuras tu aplicación; son donde cada entorno la enciende. Cuando esa distinción se te vuelve instinto, abres esos ficheros solo cuando de verdad tienes algo que decirle a un entorno concreto sobre cómo arrancar, y los dejas en paz el resto del tiempo, que es casi siempre.
- Lee
entry-clienty explica por qué la misma llamada amountsirve tanto para hidratar en SSR como para renderizar en SPA. - En
entry-server, identifica dónde vanassets,childrenyscriptsy razona qué se rompería si intercambiaras el sitio de dos de ellos. - Cambia el
iddel contenedor enentry-serversin tocarentry-clienty observa cómo se comporta la página; luego repáralo y explica el contrato. - Añade
<html lang="es">y una etiqueta meta base en el documento del servidor, y argumenta por qué eso sí pertenece a un entry point. - Toma tres cambios —un proveedor de contexto, registrar un service worker, fijar el idioma del documento— y decide para cada uno si va a
app.tsx, aentry-cliento aentry-server, justificando el destino.