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:
<!-- 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:
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 clienteEl HTML que devuelve el servidor tiene este aspecto:
<!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:
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 funcionanLa 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.
(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 interactivaEs 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:
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 perfectoISR (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:
<!-- 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.
<!-- 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>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.
Paso 2: Open Graph, Twitter Card y canonical
- Sobre el tier "ok", añades las etiquetas Open Graph: og:title, og:description, og:image (URL absoluta, 1200x630 px), og:type y og:url.
- Añades las etiquetas de Twitter Card: twitter:card (summary_large_image), twitter:title, twitter:description y twitter:image.
- Añades <link rel="canonical"> con la URL oficial de tu app desplegada.
- Puedes pegar la URL en el validador de Open Graph de Meta (developers.facebook.com/tools/debug/) y la tarjeta de previsualización sale correctamente.
Paso 3: JSON-LD, noscript y reflexión sobre el techo del CSR
- Sobre el tier "mejor", añades un bloque <script type="application/ld+json"> con datos estructurados que describen la app como WebApplication usando el vocabulario de schema.org.
- Añades un <noscript> en el body con un aviso de que la app requiere JavaScript.
- En el index.html (como comentario) o en tu reflexión personal, explicas el techo del CSR para el SEO de contenido: los metadatos del head funcionan porque son HTML estático, pero el contenido de los héroes no existe en el HTML inicial y un buscador no puede indexarlo.
- Puedes explicar qué cambiaría con SSR o SSG: el view-source mostraría los héroes y el buscador podría indexarlos uno a uno.
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".
<!doctype html>
<!-- TIER MEJOR — sobre el tier "ok", añade Open Graph, Twitter Card y canonical.
Estas etiquetas controlan la tarjeta de previsualización que aparece cuando
alguien pega el enlace en redes sociales, Slack, WhatsApp o iMessage.
Supera al tier "ok" porque el SEO no es solo para buscadores: gran parte
del tráfico llega a través de enlaces compartidos en mensajería y redes. -->
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Título y descripción para buscadores (igual que el tier "ok"). -->
<title>Team Builder — Crea tu equipo de Overwatch</title>
<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."
/>
<!-- CANONICAL — le dice al buscador cuál es la URL oficial de esta página.
Útil cuando la app es accesible desde varias URLs (con y sin trailing slash,
con o sin www, desde la URL de GitHub Pages y desde un dominio propio).
Sin canonical, el buscador puede pensar que son páginas duplicadas y
dividir el "peso" del SEO entre ellas. -->
<link rel="canonical" href="https://tu-usuario.github.io/team-builder/" />
<!-- OPEN GRAPH — el estándar que leen Facebook, LinkedIn, Slack, WhatsApp,
Telegram y la mayoría de plataformas para generar la tarjeta de previsualización
cuando alguien pega un enlace. Sin estas etiquetas, cada plataforma intenta
adivinar el título y la imagen con resultados impredecibles. -->
<!-- og:title — el título que aparece en la tarjeta (puede ser distinto del
<title> de la pestaña, aunque a menudo es el mismo). -->
<meta property="og:title" content="Team Builder — Crea tu equipo de Overwatch" />
<!-- og:description — el texto bajo el título en la tarjeta de previsualización. -->
<meta
property="og:description"
content="Filtra héroes por rol, consulta estadísticas de victorias y construye la composición perfecta para cada partida."
/>
<!-- og:image — la imagen que aparece en la tarjeta. Debe ser una URL absoluta
(no relativa) porque la plataforma la descarga desde sus servidores.
Tamaño recomendado: 1200x630 px para que se vea bien en todas las plataformas. -->
<meta
property="og:image"
content="https://tu-usuario.github.io/team-builder/og-image.png"
/>
<!-- og:type — el tipo de contenido. "website" es el valor por defecto para
aplicaciones web genéricas. Otros valores: "article", "video.movie", etc. -->
<meta property="og:type" content="website" />
<!-- og:url — la URL canónica de la página, igual que el <link rel="canonical">.
Algunas plataformas la usan para verificar que la URL coincide con la que
el usuario está compartiendo. -->
<meta property="og:url" content="https://tu-usuario.github.io/team-builder/" />
<!-- TWITTER CARD — Twitter (ahora X) tiene su propio sistema de meta tags
además de leer Open Graph. Si no están, Twitter cae back a og:* cuando
puede, pero el control es menor. -->
<!-- twitter:card — el tipo de tarjeta. "summary_large_image" muestra la imagen
a ancho completo encima del texto. "summary" muestra una miniatura pequeña
a la izquierda. Para una app con imagen llamativa, large_image es la mejor opción. -->
<meta name="twitter:card" content="summary_large_image" />
<!-- twitter:title — el título de la tarjeta en Twitter. -->
<meta name="twitter:title" content="Team Builder — Crea tu equipo de Overwatch" />
<!-- twitter:description — la descripción bajo el título en la tarjeta de Twitter. -->
<meta
name="twitter:description"
content="Filtra héroes por rol, consulta estadísticas de victorias y construye la composición perfecta."
/>
<!-- twitter:image — la imagen de la tarjeta. También debe ser URL absoluta. -->
<meta
name="twitter:image"
content="https://tu-usuario.github.io/team-builder/og-image.png"
/>
<!-- LÍMITE DEL TIER MEJOR:
Las meta tags y el canonical están bien cubiertos. Lo que falta es
decirle a los buscadores QUÉ TIPO de aplicación es esta y qué hace,
de forma estructurada y legible por máquina. Para eso existen los
datos estructurados en formato JSON-LD, que verás en el tier "excelente". -->
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html> Por qué es mejor que el anterior
- Open Graph, Twitter Card y canonical cubren el segundo frente del SEO: el tráfico que llega a través de enlaces compartidos. Gran parte del tráfico de una app no viene de Google directamente, sino de alguien que pegó el enlace en Slack o en un grupo de WhatsApp. Sin og:image, esa previsualización sale vacía o con una imagen aleatoria.
- Su límite: los buscadores aún tienen que inferir qué tipo de aplicación es esta a partir del HTML. El tier "excelente" les da esa información de forma estructurada y legible por máquina con JSON-LD.
<!doctype html>
<!-- TIER EXCELENTE — sobre el tier "mejor", añade datos estructurados JSON-LD
y un <noscript> de aviso. Supera al tier "mejor" porque los datos estructurados
le dan al buscador una descripción legible por máquina de qué es la aplicación,
lo que puede activar "rich results" (resultados enriquecidos) en Google:
bloques especiales con más información en la página de resultados. -->
<html lang="es">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Título y descripción para buscadores. -->
<title>Team Builder — Crea tu equipo de Overwatch</title>
<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."
/>
<!-- URL canónica para evitar contenido duplicado entre distintas URLs de la app. -->
<link rel="canonical" href="https://tu-usuario.github.io/team-builder/" />
<!-- Open Graph para la tarjeta de previsualización en redes y mensajería. -->
<meta property="og:title" content="Team Builder — Crea tu equipo de Overwatch" />
<meta
property="og:description"
content="Filtra héroes por rol, consulta estadísticas de victorias y construye la composición perfecta para cada partida."
/>
<meta
property="og:image"
content="https://tu-usuario.github.io/team-builder/og-image.png"
/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://tu-usuario.github.io/team-builder/" />
<!-- Twitter Card para el control específico de la tarjeta en Twitter/X. -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Team Builder — Crea tu equipo de Overwatch" />
<meta
name="twitter:description"
content="Filtra héroes por rol, consulta estadísticas de victorias y construye la composición perfecta."
/>
<meta
name="twitter:image"
content="https://tu-usuario.github.io/team-builder/og-image.png"
/>
<!-- JSON-LD (JavaScript Object Notation for Linked Data) — datos estructurados
que los buscadores leen para entender el contexto de la página de forma
precisa, sin inferir nada del HTML. Google los usa para generar "rich results":
bloques enriquecidos en la página de resultados (valoraciones, preguntas
frecuentes, migas de pan...). El tipo "WebApplication" describe una app web. -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Team Builder — Overwatch",
"description": "Herramienta para crear y gestionar equipos de Overwatch. Filtra héroes por rol, consulta estadísticas de victorias y construye la composición perfecta.",
"url": "https://tu-usuario.github.io/team-builder/",
"applicationCategory": "GameApplication",
"operatingSystem": "Navegador web",
"inLanguage": "es",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "EUR"
}
}
</script>
<!-- EL TECHO DEL CSR PARA EL SEO DE CONTENIDO:
Todo lo que has añadido hasta aquí (title, description, Open Graph,
Twitter Card, canonical y JSON-LD) vive en el <head> del index.html
estático. Los buscadores y las redes lo leen directamente, sin ejecutar
JavaScript. Hasta aquí, todo bien.
Ahora haz esta prueba: abre tu app desplegada y pulsa Ctrl+U (ver código
fuente). Verás el <head> completo con todas tus meta tags. Pero el <body>
aparecerá así:
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
Los héroes no están ahí. El view-source muestra el HTML que el servidor
envió, antes de que React ejecutara el JavaScript. Un buscador que no
ejecute bien tu JavaScript no ve ningún héroe, no puede indexar ficha
por ficha, ni generar una URL propia para cada rol o personaje.
Eso es el techo del CSR (Client-Side Rendering) para el SEO de contenido:
los metadatos globales de la app sí funcionan (están en el HTML estático),
pero el contenido dinámico no existe en el HTML inicial.
Para superar ese techo haría falta SSR (Server-Side Rendering) o SSG
(Static Site Generation): tecnologías como Astro o Next.js que pintan el
HTML completo en el servidor antes de enviarlo, de forma que el view-source
muestra los héroes y el buscador puede indexarlos uno a uno. Lo verás en
sus apéndices. -->
</head>
<body>
<!-- React monta la aplicación aquí. Antes de que el JavaScript arranque,
este div está vacío: eso es el CSR en acción. -->
<div id="root"></div>
<!-- Aviso para usuarios con JavaScript desactivado. El navegador muestra este
bloque solo cuando el JavaScript está bloqueado o desactivado. React no
puede arrancar sin JavaScript, así que sin este aviso el usuario vería
una página en blanco sin ninguna explicación. -->
<noscript>
Esta aplicación necesita JavaScript para funcionar. Por favor, activa JavaScript
en tu navegador para poder usar el Team Builder de Overwatch.
</noscript>
<!-- El punto de entrada de Vite. Carga main.tsx, que arranca React. -->
<script type="module" src="/src/main.tsx"></script>
</body>
</html> Por qué es mejor que el anterior
- JSON-LD es el formato que Google prefiere para los datos estructurados: le dices explícitamente qué tipo de aplicación es (WebApplication), cómo se llama, qué hace y en qué idioma está. Eso puede activar "rich results" en los resultados de búsqueda: bloques enriquecidos con más información que un resultado normal.
- El comentario sobre el techo del CSR es la lección más importante del tier: aunque el head esté perfecto, el contenido dinámico (los héroes) no existe en el HTML inicial. Para indexar ese contenido por separado haría falta SSR o SSG, donde el view-source mostraría los héroes y el buscador podría indexarlos ficha a ficha.