learning-front

Nivel 9 · El ciclo completo: del commit al deploy

SPA, SSR, SSG y SEO

Dónde se renderiza tu app —en el cliente (CSR), en el servidor (SSR) o en el build (SSG)— y qué implica para el SEO: por qué una SPA sale con el body vacío en el view-source, qué resuelven las meta tags y cuándo necesitas Next o Astro.

En el capítulo 4 desplegaste tu Team Builder en internet. Si lo tienes desplegado, ábrelo ahora y pulsa Ctrl+U (o clic derecho → Ver código fuente). Verás algo así en el body:

html
<!-- El div contenedor que React usa como punto de montaje -->
<div id="root"></div>

<!-- El punto de entrada de Vite: carga main.tsx, que arranca React -->
<script type="module" src="/src/main.tsx"></script>

Los héroes no están ahí. En pantalla ves la lista completa, los filtros, las estadísticas. Pero en el código fuente, el body está casi vacío. ¿Por qué?

Porque tu app se renderiza en el cliente. El servidor envía ese HTML vacío más el bundle de JavaScript; el navegador descarga el bundle, lo ejecuta, y React pinta los héroes dentro del div#root. El view-source muestra lo que llegó antes de que se ejecutara ningún JavaScript.

Este capítulo explica los tres sitios donde puede renderizarse una app, qué implica cada uno, y por qué eso afecta a que Google te encuentre.

CSR: renderizar en el cliente (donde estás ahora)#

CSR son las siglas de Client-Side Rendering, renderizado en el cliente. Es el modelo que usa tu Team Builder: el servidor envía un HTML casi vacío más el bundle de JavaScript, y el navegador hace todo el trabajo de construir la interfaz.

El flujo es este:

texto
usuario pide la URL en el navegador


  servidor devuelve el index.html (casi vacío)


  navegador descarga el bundle de JavaScript (main.tsx + dependencias)


  React ejecuta el código y pinta los héroes en #root


  la app está lista: navegación sin recarga, estado en cliente

El HTML que devuelve el servidor tiene este aspecto:

html
<!doctype html>
<!-- El servidor envía este fichero tal cual; no ejecuta ningún código -->
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <!-- El título que aparece en la pestaña del navegador -->
    <title>Team Builder</title>
  </head>
  <body>
    <!-- React monta la app aquí; antes de ejecutar el JS, está vacío -->
    <div id="root"></div>

    <!-- Vite inyecta el bundle de tu app aquí -->
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Las ventajas son reales: el hosting es barato (son ficheros estáticos, como viste en el capítulo 4), no necesitas un servidor que ejecute código, y una vez que el bundle se ha descargado, la navegación entre rutas es instantánea porque React gestiona el enrutado en el cliente sin pedir nada al servidor.

El coste también es real: hasta que el bundle se descarga y ejecuta, el usuario ve una pantalla en blanco. Y el body vacío en el HTML inicial es el principal problema para el SEO, como verás más adelante.

SSR: renderizar en el servidor#

SSR son las siglas de Server-Side Rendering, renderizado en el servidor. En este modelo, el servidor ejecuta el componente de React y devuelve el HTML ya con el contenido incluido, en cada petición.

El flujo cambia completamente:

texto
usuario pide la URL en el navegador


  servidor recibe la petición


  servidor ejecuta el componente de React (con los datos de ese momento)


  servidor devuelve el HTML con los héroes ya pintados


  navegador muestra el contenido al instante (primer pintado inmediato)


  navegador descarga el JS y React HIDRATA el HTML existente


  la app es interactiva: botones, filtros y estado funcionan

La hidratación es el paso clave. Cuando el servidor envía el HTML con los héroes, el navegador lo muestra de inmediato: el usuario ve la lista antes de que baje ningún JavaScript. Pero esa página es estática: los botones de filtro no responden porque React aún no está activo. La hidratación es el proceso en que React descarga el bundle, lo ejecuta y “revive” ese HTML ya existente: añade los event listeners, sincroniza el estado interno y convierte la página en una app interactiva.

El “¿y qué?” de la hidratación: hasta que termina, el usuario ve los héroes pero los filtros no hacen nada si les hace clic. En apps bien optimizadas este tiempo es de menos de un segundo; en apps con bundles grandes puede durar varios segundos. Es un estado transitorio inevitable en SSR.

A cambio de esa complejidad, SSR resuelve los dos problemas del CSR: el primer pintado es inmediato (hay contenido en el HTML desde el primer byte) y el buscador ve los héroes porque están en el HTML que devuelve el servidor.

El coste: necesitas un servidor que ejecute JavaScript (Node.js), no un hosting estático. Eso es más complejo y más caro que lo que configuraste en el capítulo 4. El backend con Node.js lo verás en el Nivel 10.

SSG: generar en el build#

SSG son las siglas de Static Site Generation, generación estática. Es el punto medio: el HTML con el contenido se genera una sola vez, durante el build, y el resultado son ficheros HTML estáticos que el servidor sirve directamente.

texto
(una sola vez, en el build)


  herramienta ejecuta los componentes y genera ficheros .html
  con el contenido ya incluido


  los ficheros .html se suben al hosting estático


(en cada petición del usuario)


  servidor devuelve el fichero .html directamente (sin ejecutar código)


  usuario ve el contenido al instante, como con SSR


  el JS hidrata la página para volverla interactiva

Es lo mejor de los dos mundos para contenido que cambia poco: el HTML llega con el contenido incluido (bueno para el SEO y para el primer pintado), pero se sirve desde un hosting estático (sin servidor que mantener, barato, rápido).

Este curso funciona así. Cada lección que lees es un fichero HTML que Astro generó durante el build. El servidor no ejecuta ningún código cuando pides esta página: devuelve el fichero directamente. Por eso el view-source de cualquier lección del curso muestra el texto completo, no un div vacío.

El límite del SSG: el contenido del HTML refleja los datos del momento del build. Si el Team Builder tuviera datos en tiempo real (partidas jugadas en los últimos cinco minutos, estado del servidor de Overwatch), esos datos estarían desactualizados en el HTML generado. Para ese caso hace falta SSR.

El espectro, no un interruptor#

No es un interruptor binario entre CSR y SSR. Es un espectro, y los frameworks modernos permiten elegir la estrategia por ruta:

texto
CUÁNDO SE GENERA EL HTML CON EL CONTENIDO

   en el cliente          en el build          en el servidor
       │                      │                      │
       ▼                      ▼                      ▼
      CSR      ───────      SSG      ───── ISR ─────  SSR
  (SPA pura)            (estático,           (cada N       (en cada
                        en el build)          minutos)      petición)

  más simple                                          más potente
  hosting estático                           necesita servidor
  SEO difícil                                  SEO perfecto

ISR (Incremental Static Regeneration) es una variante de SSG que añaden algunos frameworks como Next.js: la página se genera estáticamente en el build, pero el servidor la regenera automáticamente cada cierto tiempo (cada hora, cada día) sin necesidad de un deploy nuevo. Útil para contenido que cambia con frecuencia moderada, como un listado de noticias.

En una app real, cada ruta puede usar una estrategia diferente. El panel de un dashboard detrás de login puede ser CSR puro. La landing pública que explica la app puede ser SSG. Un listado de productos con precio en tiempo real puede ser SSR. Todo en la misma app, cada página con la estrategia que mejor encaja.

SEO: que un buscador entienda tu página#

SEO son las siglas de Search Engine Optimization, optimización para motores de búsqueda. La pregunta que responde el SEO es: ¿cómo hace Google para encontrar tu página y mostrarla en los resultados cuando alguien busca “team builder overwatch”?

Un crawler (el robot que recorre la web) descarga el HTML de cada URL que encuentra y lee su contenido. El texto del HTML es lo que indexa. Con CSR, ese HTML tiene el body vacío: el crawler ve el div#root pero no los héroes. El resultado: Google no puede mostrar “Reinhardt — tanque, 82% victorias” como resultado de búsqueda porque ese texto nunca llegó en el HTML.

Google puede ejecutar JavaScript, pero no siempre lo hace bien ni a tiempo. El presupuesto de renderizado por dominio es limitado: Googlebot puede tardar días en procesar el JavaScript de tu app, y mientras tanto la versión indexada es la del HTML vacío.

El “¿y qué?” práctico: tu Team Builder puede no aparecer en Google, o aparecer con el título “Vite + React + TS” y sin descripción porque eso era lo que tenía el index.html por defecto de Vite. Alguien que busca “herramienta team builder overwatch” no te encuentra.

Meta tags: el mínimo de SEO que funciona con CSR#

Aunque el body de tu SPA llegue vacío, el head es HTML estático que el servidor incluye en el fichero original, antes de que React arranque. Eso significa que puedes enriquecer el head con meta tags sin necesitar SSR, y los buscadores las leerán directamente.

Las dos más importantes:

html
<!-- El título que Google muestra en los resultados de búsqueda.
     Máximo 60 caracteres; debe ser único y descriptivo. -->
<title>Team Builder — Crea tu equipo de Overwatch</title>

<!-- El texto bajo el título en los resultados de búsqueda.
     No afecta directamente al ranking, pero sí a si el usuario hace clic.
     Máximo 160 caracteres. -->
<meta
  name="description"
  content="Crea y gestiona tu equipo ideal de Overwatch. Filtra héroes por rol y construye la composición perfecta."
/>

El “¿y qué?” de no tenerlas: Google muestra “Vite + React + TS” como título de tu app en los resultados de búsqueda. Sin descripción, Google elige el texto que quiera de la página (o no muestra nada). La tasa de clics se desploma.

Open Graph: la tarjeta al compartir un enlace#

Cuando alguien pega la URL de tu app en Slack, WhatsApp, Twitter o LinkedIn, la plataforma descarga el HTML de esa URL y busca las meta tags de Open Graph para construir la tarjeta de previsualización. Sin ellas, la tarjeta sale vacía o con una imagen aleatoria.

html
<!-- og:title — el título de la tarjeta de previsualización -->
<meta property="og:title" content="Team Builder — Crea tu equipo de Overwatch" />

<!-- og:description — el texto bajo el título en la tarjeta -->
<meta
  property="og:description"
  content="Filtra héroes por rol, consulta estadísticas de victorias y construye la composición perfecta."
/>

<!-- og:image — la imagen de la tarjeta. Debe ser una URL absoluta (no relativa)
     porque la plataforma la descarga desde sus servidores.
     Tamaño recomendado: 1200x630 px. -->
<meta
  property="og:image"
  content="https://tu-usuario.github.io/team-builder/og-image.png"
/>

<!-- og:type — el tipo de contenido de la página -->
<meta property="og:type" content="website" />

El “¿y qué?” de no tenerlas: pegas el enlace de tu Team Builder en el canal del equipo y sale un bloque gris sin imagen, sin título y sin descripción. Nadie hace clic porque no saben qué van a encontrar.

Estas etiquetas funcionan en tu SPA con CSR porque viven en el head del HTML estático, no en el contenido que pinta React. Eso es exactamente lo que vas a trabajar en el ejercicio.

Cuándo cada estrategia#

No hay una respuesta universal. La decisión depende de lo que necesite la app:

CSR va perfecto cuando:

  • La app está detrás de un login (un dashboard, un panel de gestión, una herramienta interna). Google no puede acceder a esas páginas de todas formas, así que el SEO es irrelevante. El hosting estático es más barato y simple que mantener un servidor de renderizado.
  • La app actualiza datos en tiempo real de forma muy frecuente (cada pocos segundos). SSR también puede manejar esto, pero el CSR es más sencillo de implementar para ese caso.

SSG va perfecto cuando:

  • El contenido es público y cambia poco: una landing de marketing, la documentación de un producto, un blog, las reglas de un juego. Pagas una vez el coste del build y sirves ficheros estáticos para siempre.
  • Necesitas SEO y el contenido no cambia entre deploys.

SSR va perfecto cuando:

  • El contenido es público, necesita SEO, y cambia con frecuencia o depende del usuario que hace la petición (una tienda con precios en tiempo real, una página de perfil personalizada).
  • Necesitas el primer pintado más rápido posible aunque haya un servidor detrás.

La mayoría de las apps de empresa son SPAs con CSR: dashboards internos, paneles de administración, herramientas de gestión de equipo. Por eso el CSR sigue siendo el modelo dominante en el día a día de trabajo, aunque el SEO de contenido sea su talón de Aquiles.

Next y Astro, de concepto#

Dos frameworks resuelven los escenarios donde el CSR no es suficiente:

Next.js es el meta-framework de React. Añade SSR, SSG e ISR a React con una convención de carpetas: cada fichero en pages/ (o en app/ con la nueva arquitectura) define una ruta, y puedes anotar cada página con funciones especiales que indican si debe generarse en el servidor en cada petición, en el build, o regenerarse cada cierto tiempo. Es el estándar de facto para apps React que necesitan SEO o primer pintado optimizado. Una empresa que necesita que su web pública aparezca en Google y que su panel interno sea una SPA rápida puede usar Next.js para las dos cosas en el mismo proyecto.

Astro está orientado al contenido estático. Su modelo por defecto es SSG: genera HTML en el build y envía muy poco JavaScript al navegador. La pieza diferencial de Astro es el modelo de islas: el HTML de la página es estático, y solo los componentes interactivos (un carrusel, un buscador, un formulario) se hidratan como islas de JavaScript independientes. El resto de la página llega al navegador como HTML puro, sin JavaScript que descargar ni ejecutar. Este curso está construido con Astro por exactamente ese motivo: las lecciones son contenido estático; solo los playgrounds, los quizzes y los ejercicios se hidratan.

Los dos los verás en profundidad en sus apéndices del curso. Por ahora, lo que importa es entender qué resuelven y cuándo los necesitas: cuando el CSR ya no es suficiente para lo que la app necesita hacer.

Comprueba lo que sabes#

Pregunta 1 de 7

Evalúa el siguiente código y responde:

<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <title>Team Builder</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
<!-- PREGUNTA: ¿Por qué el body de una SPA aparece vacío al pulsar Ctrl+U (ver código fuente)? -->

Tu turno#

El ejercicio es en local, sobre el index.html de la raíz de tu Team Builder de React. No hay playground: las meta tags de Open Graph y Twitter Card solo se pueden verificar de verdad con la app desplegada (o con la extensión “Meta Tag Viewer” del navegador). Las soluciones muestran el index.html completo de cada tier con comentarios que explican cada decisión. Haz el tuyo primero y luego compara.

Ejercicio · hazlo en local

Meta tags de SEO en el index.html del Team Builder

Abre el index.html de la raíz de tu Team Builder de React y enriquece su <head> con meta tags de SEO. El <body> no cambia: sigue siendo el <div id="root"> de siempre. Las soluciones muestran el index.html completo de cada tier con comentarios que explican cada decisión. Haz el tuyo primero y luego compara.

Paso 1: Lo mínimo: título descriptivo y meta de descripción

  • Cambias el <title> por uno descriptivo de la app (no "Vite + React + TS").
  • Añades <meta name="description"> con 1-2 frases que resumen qué hace la app (máximo 160 caracteres).
  • Compruebas que el <meta charset> y el <meta name="viewport"> siguen siendo los primeros del head.
  • Si abres la app en el navegador, el título correcto aparece en la pestaña.

Cómo hacerlo en local

Clona el repositorio del curso, entra en la carpeta del ejercicio y abre el index.html en tu navegador. Toda tu solución va en solucion.js.

git clone <repo>
cd exercises/nivel-9/spa-ssr-ssg-y-seo
# abre index.html en el navegador y edita solucion.js
Ver soluciones
<!doctype html>
<!-- TIER OK — lo mínimo de SEO: título descriptivo y meta de descripción.
     Sin esto, Google muestra "Vite + React + TS" como título y no tiene
     descripción que mostrar bajo el enlace en los resultados de búsqueda.
     El body no cambia respecto al index.html por defecto de Vite: el contenido
     lo sigue pintando React en el cliente. -->

<!-- El atributo lang ayuda a los buscadores y lectores de pantalla a saber
     en qué idioma está escrita la página. -->
<html lang="es">
  <head>
    <!-- El juego de caracteres debe ser lo primero del head para que el navegador
         interprete correctamente el resto del documento desde el inicio. -->
    <meta charset="UTF-8" />

    <!-- El favicon que aparece en la pestaña del navegador. -->
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />

    <!-- En móviles, sin este meta el navegador haría zoom-out para encajar toda
         la página en pantalla. Con width=device-width, usa el ancho real del
         dispositivo. initial-scale=1 evita el zoom automático al cargar. -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <!-- El título que aparece en la pestaña del navegador, en los resultados de
         búsqueda de Google y cuando alguien guarda la página en favoritos.
         Debe describir la página de forma concisa y única (máximo ~60 caracteres). -->
    <title>Team Builder — Crea tu equipo de Overwatch</title>

    <!-- La descripción que Google muestra bajo el título en los resultados.
         No influye directamente en el ranking, pero sí en si el usuario hace clic.
         Debe resumir qué hace la página en 1-2 frases (máximo ~160 caracteres). -->
    <meta
      name="description"
      content="Crea y gestiona tu equipo ideal de Overwatch. Filtra héroes por rol, consulta estadísticas de victorias y construye la composición perfecta."
    />

    <!-- LÍMITE DEL TIER OK:
         Estas dos etiquetas mejoran cómo te ve Google, pero no controlan
         la tarjeta de previsualización que aparece al pegar el enlace en
         Slack, WhatsApp o Twitter. Para eso hacen falta las etiquetas
         Open Graph y Twitter Card, que verás en el tier "mejor". -->
  </head>
  <body>
    <!-- React monta toda la aplicación dentro de este div.
         Antes de que el JavaScript se descargue y ejecute, este div está vacío:
         eso es el CSR (Client-Side Rendering). Si haces Ctrl+U para ver el
         código fuente de la página publicada, verás este div vacío aunque en
         pantalla veas los héroes. -->
    <div id="root"></div>

    <!-- El punto de entrada de Vite. Carga tu main.tsx, que a su vez arranca React. -->
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Por qué este nivel

  • Lo mínimo que cualquier página pública debería tener: un título que describe la app y una meta de descripción que Google puede mostrar bajo el enlace en los resultados de búsqueda. Con el título por defecto de Vite ("Vite + React + TS"), Google no tiene nada útil que mostrar.
  • Su límite: estas dos etiquetas mejoran cómo te ve Google, pero no controlan la tarjeta de previsualización que aparece al pegar el enlace en Slack, WhatsApp o redes sociales. Para eso hace falta Open Graph y Twitter Card, que añade el tier "mejor".