Lograr que una aplicación renderizada desde el servidor funcione sin duplicar trabajo en el cliente es uno de los retos más importantes al implementar server side render. El proceso se resume en tres pasos claros: convertir componentes a string, hidratar los eventos en el cliente y gestionar el estado inicial de forma eficiente y segura.
¿Por qué el doble renderizado es un problema?
Cuando se renderiza todo a un string en el servidor y luego se vuelve a renderizar completamente en el cliente, se está haciendo un trabajo innecesario. Es como ir a comprar leche, regresar a casa y volver a salir por la misma leche: se gasta el doble de recursos sin obtener ningún beneficio adicional.
El método hydrate de React DOM resuelve este problema. A diferencia de un render completo, hydrate está diseñado para recibir el HTML que ya viene del servidor y solo asociar los eventos necesarios del lado del cliente, como onClick, onLoad o cualquier otro event listener que no puede existir en una cadena de caracteres pura [01:15].
¿Cómo funciona el proceso de hidratación?
El flujo consta de dos pasos fundamentales:
- Render a string: el conjunto de componentes se convierte en una cadena de caracteres en el servidor, sin eventos asociados.
- Hydrate en el cliente: cuando el HTML llega al navegador, React hidrata el contenido agregando todos los event listeners que la aplicación necesita [02:05].
Esto significa que el usuario recibe un feedback inmediato con el HTML pre-renderizado, y luego la aplicación se vuelve interactiva sin necesidad de reconstruir todo el DOM.
¿Qué es el preloaded state y cómo se implementa?
Otro problema de eficiencia aparece cuando el estado inicial se define dos veces: una en el servidor y otra en el cliente. Cargar la misma configuración dos veces es redundante. La solución es crear un preloaded state, un estado precargado que viaja desde el servidor hacia el cliente [03:05].
Para implementarlo se define una constante que obtiene el estado actual del store:
javascript
const preloadedState = store.getState();
Este estado se pasa a la función setResponse, que construye el HTML de respuesta. Siguiendo las recomendaciones de la documentación de Redux para server side render, se inyecta el estado dentro de un script en el HTML [03:45]:
html
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)}
</script>
El JSON.stringify convierte el objeto de estado en un string que puede ser interpretado correctamente del lado del cliente.
¿Cómo se accede al estado precargado desde el cliente?
En el archivo del front end, se elimina la referencia al initial state local y se reemplaza por el estado que viene inyectado en window [05:10]:
javascript
const preloadedState = window.PRELOADED_STATE;
const store = createStore(reducer, preloadedState);
De esta forma, el store se crea con el mismo estado que se configuró en el servidor, evitando duplicaciones.
¿Cómo proteger el estado para que no sea accesible por el usuario?
Una vez que el preloaded state queda expuesto en window, cualquier usuario puede abrir la consola del navegador y acceder a los datos con window.__PRELOADED_STATE__. Esto representa un riesgo de seguridad [06:00].
La solución es sencilla: después de crear el store con el estado precargado, se elimina la referencia del objeto global:
javascript
delete window.PRELOADED_STATE;
Al refrescar el navegador, la aplicación sigue funcionando correctamente, pero el usuario ya no puede acceder al estado desde la consola [06:30].
En resumen, el flujo completo de server side render optimizado sigue esta secuencia:
- renderToString: genera el HTML inicial para un feedback inmediato.
- hydrate: asocia los eventos del cliente sin re-renderizar.
- preloaded state: envía el estado del servidor al cliente de forma eficiente.
- delete del preloaded state: protege los datos eliminando la referencia pública.
Cada paso construye sobre el anterior para lograr una experiencia rápida, eficiente y segura. ¿Has implementado alguna estrategia diferente para manejar el estado en server side render? Comparte tu experiencia.