Cómo inyectar estilos de SSR sin flickering

Resumen

Cuando montas una aplicación con Server-Side Rendering y usas Styled Components, los estilos no aparecen en la primera respuesta del servidor. El resultado es un flickering feo entre la versión sin estilos y la versión hidratada. Aquí verás cómo extraer e inyectar los estilos en el HTML inicial usando la API oficial de Styled Components.

¿Por qué la app se ve sin estilos en el primer render?

Cuando recargas la app y desactivas JavaScript, notas que el HTML del servidor llega deplorable, sin colores ni formato. La razón es simple: Styled Components genera los estilos en tiempo de ejecución dentro del navegador, así que el servidor envía un HTML pelón y los estilos solo aparecen cuando JavaScript se hidrata en el cliente.

Ese salto visual es lo que se conoce como flickering y arruina la experiencia incluso cuando técnicamente el SSR ya funciona [01:05].

¿Qué causa el flickering en SSR con Styled Components? El servidor envía HTML sin CSS porque Styled Components inyecta estilos en el cliente. Hasta que JavaScript se ejecuta, la página se ve sin formato.

¿Cómo extraer los estilos con ServerStyleSheet?

La solución la trae la propia documentación de Styled Components en su sección de Server-Side Rendering. La mayoría de frameworks de CSS, como Material UI o Tailwind, ofrecen una guía equivalente porque la integración con SSR es un caso común [01:55].

La API se llama ServerStyleSheet y funciona en tres pasos:

  1. Crear una instancia de ServerStyleSheet.
  2. Envolver tu aplicación con sheet.collectStyles() antes de renderizarla.
  3. Extraer las etiquetas con sheet.getStyleTags() e inyectarlas en el template HTML.

La fórmula se repite en casi todos los extractores de CSS para SSR: una instancia hace wrap de tu app, recolecta los estilos durante el render y luego te devuelve un string listo para meter en el <head>.

¿Cómo se implementa paso a paso en el render?

En el archivo de render, primero importa ServerStyleSheet desde styled-components y envuelve la lógica en un try/catch para capturar errores con console.error.

Dentro del try, el flujo queda así:

js const sheet = new ServerStyleSheet(); try { const html = renderToString(sheet.collectStyles(<App />)); const styleTags = sheet.getStyleTags(); return template(html, initialProps, styleTags); } catch (error) { console.error(error); }

Un detalle que es fácil pasar por alto: el método correcto es getStyleTags(), no styleTags como propiedad. Ese pequeño tropiezo aparece en la implementación real [05:20] y vale la pena tenerlo presente.

¿Cómo se inyectan los styleTags en el template?

El template que arma el HTML necesita un tercer parámetro para recibir los estilos. Lo agregas como interpolación dentro del <head>:

js function template(html, initialProps, styles) { return <!DOCTYPE html> <html> <head> ${styles} </head> <body> <div id="app">${html}</div> </body> </html> ; }

Al recargar, el flickering desaparece. Si vuelves a desactivar JavaScript y revisas el <head> en DevTools, vas a ver una etiqueta <style data-styled="true"> con todos los estilos precargados desde el servidor.

¿Qué hace sheet.getStyleTags()? Devuelve un string con las etiquetas <style> que contienen el CSS generado por tu app durante el render del servidor. Ese string se inserta directamente en el HTML inicial.

¿Qué pasa con las etiquetas después de hidratar?

Una vez que el cliente carga el bundle de JavaScript, Styled Components vuelve a tomar control de los estilos. La etiqueta data-styled que vino del servidor sigue ahí, igual que el <script> con window.__INITIAL_PROPS__.

Una buena práctica que siguen varios motores de SSR es remover esas etiquetas del DOM una vez que la app se hidrata, porque ya no se usan:

  • El script de initial props solo sirve durante la hidratación.
  • La etiqueta de estilos del servidor queda redundante cuando Styled Components arranca en el cliente.
  • Limpiarlas reduce el peso del DOM y deja el HTML más prolijo.

Es un reto opcional, pero útil si te interesa pulir tu motor [09:10].

¿Funciona lo mismo con otros frameworks de CSS?

La mecánica es similar en Material UI y existen alternativas con plugins de Webpack como MiniCssExtractPlugin, que extrae el CSS en archivos separados y los inserta directamente. Cada extractor tiene su propia API, por eso siempre conviene revisar la documentación oficial antes de implementar.

Lo importante es entender el patrón: instancia, collect, extract, inject. Con eso puedes adaptar la solución a casi cualquier librería de estilos.

Con esta pieza, tu motor de SSR ya entrega HTML completo y estilizado en la primera respuesta. ¿Cuentas tu experiencia integrando estilos con SSR en los comentarios?