Cómo logramos previsualizaciones enriquecidas en WhatsApp, Twitter y posicionamiento SEO en una Single Page Application (Angular) sin desplegar un servidor SSR completo ni perder el rendimiento de una PWA.

1. El eterno dilema de las Single Page Applications (SPA)
Las Single Page Applications (Angular, React, Vue o Svelte) ofrecen una experiencia de usuario inmejorable: transiciones fluidas, funcionamiento sin conexión como PWA, acceso a APIs nativas (cámara para escanear códigos de barras, geolocalización de farmacias, síntesis de voz, etc.) y un despliegue estático barato y escalable.
Sin embargo, tienen un talón de Aquiles histórico: el compartir en redes sociales y el SEO.
Cuando un usuario comparte el enlace de un medicamento concreto en WhatsApp o Telegram:
https://prospectofacil.com/medicamento/57620
El rastreador de WhatsApp no ejecuta JavaScript. Hace una petición GET básica y lee el index.html estático original:
<!doctype html>
<html>
<head>
<title>Prospecto Fácil</title>
<meta property="og:title" content="Prospecto Fácil">
<meta property="og:description" content="Información de medicamentos...">
</head>
<body>
<app-root></app-root>
</body>
</html>
El resultado es frustrante: todas las tarjetas de previsualización en WhatsApp muestran la descripción genérica de la home, en lugar del nombre del medicamento, su principio activo y la foto de su envase oficial.
2. La solución tradicional y sus costes: SSR Completo
La respuesta habitual de la industria es implementar Server-Side Rendering (SSR) completo (como Angular Universal / @angular/ssr, Next.js o Nuxt):
- Problema 1: Conflictos de APIs de navegador: Librerías que interactúan con la cámara (
@zxing/library), mapas interactivos (leaflet) olocalStoragefallan en el servidor si no están minuciosamente blindadas. - Problema 2: Complejidad de infraestructura: Ya no puedes alojar simples archivos estáticos en un CDN global gratuito. Necesitas contenedores Docker, Cloud Run o Node.js siempre activos, lo que incrementa la latencia y los costes de infraestructura.
- Problema 3: Hidratación y sobrecarga: El servidor gasta ciclos de CPU renderizando árboles de componentes pesados para peticiones que sólo necesitaban tres etiquetas
<meta>.
3. La tercera vía: El «Edge Bot-Proxy» con Cloudflare Pages
¿Y si los usuarios humanos siguen recibiendo la SPA estática ultrarrápida desde el CDN, y únicamente los bots y redes sociales reciben el HTML con metadatos dinámicos inyectados al vuelo en el Edge?
Esta arquitectura híbrida aprovecha dos capacidades clave de Cloudflare Pages:
- Cloudflare Pages Functions: Código serverless que corre en el Edge sobre el runtime V8 de Cloudflare Workers con latencia de un solo dígito de milisegundos.
HTMLRewriter: Una API nativa de streaming en C++ que puede parsear y reescribir fragmentos de HTML al vuelo sin tener que cargar todo el DOM en memoria.
4. Implementación paso a paso (Caso Real: Prospecto Fácil)
Paso 1: Enrutamiento en el Edge
En Cloudflare Pages, basta con crear una carpeta functions/ en la raíz del repositorio. Para interceptar cualquier medicamento, creamos: functions/medicamento/[id].ts
Paso 2: Detección precisa de Crawlers
Identificamos a los agentes que requieren metadatos estáticos inmediatos:
const BOT_REGEX =
/googlebot|bingbot|duckduckbot|facebot|facebookexternalhit|twitterbot|linkedinbot|slackbot|telegrambot|whatsapp|applebot|gptbot|claude-web|perplexitybot/i;
export const onRequest = async (context) => {
const { request, params } = context;
const userAgent = request.headers.get('user-agent') || '';
const isBot = BOT_REGEX.test(userAgent);
// Obtenemos la respuesta estática base (el index.html de la SPA)
const response = await context.next();
const medId = params.id;
Paso 3: Consulta de datos al vuelo con tolerancia a fallos
En el Edge consultamos la API oficial de CIMA (o nuestro catálogo en memoria para medicamentos frecuentes), aplicando siempre un timeout estricto para no penalizar el tiempo de respuesta:
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2000);
const cimaRes = await fetch(`https://cima.aemps.es/cima/rest/medicamento?nregistro=${encodeURIComponent(medId)}`, {
signal: controller.signal,
headers: { Accept: 'application/json' },
});
clearTimeout(timeoutId);
const med = await cimaRes.json();
Paso 4: Inyección en streaming con HTMLRewriter
Aquí es donde ocurre la magia. Sin parsear HTML con librerías pesadas como Cheerio o JSDOM, HTMLRewriter sustituye las etiquetas <meta> en streaming:
const title = `${med.nombre} — Prospecto y Posología Oficial | Prospecto Fácil`;
const desc = `Prospecto oficial y posología de ${med.nombre} (${med.pactivos}). Información clara y accesible.`;
const image = med.fotos?.[0]?.url || 'https://prospectofacil.com/icon-512.png';
let rewriter = new HTMLRewriter()
.on('title', { element(e) { e.setInnerContent(title); } })
.on('meta[property="og:title"]', { element(e) { e.setAttribute('content', title); } })
.on('meta[property="og:description"]', { element(e) { e.setAttribute('content', desc); } })
.on('meta[property="og:image"]', { element(e) { e.setAttribute('content', image); } })
.on('meta[name="twitter:card"]', { element(e) { e.setAttribute('content', 'summary_large_image'); } })
.on('head', {
element(e) {
// Inyección de microdatos Schema.org / Drug para SEO médico
e.append(`<script type="application/ld+json">${JSON.stringify(drugSchema)}</script>`, { html: true });
}
});
// Si es un bot o motor de IA, inyectamos un resumen semántico dentro de <app-root>
if (isBot) {
rewriter = rewriter.on('app-root', {
element(e) {
e.setInnerContent(semanticCrawlerHtml, { html: true });
},
});
}
return rewriter.transform(response);
5. Resultados y Ventajas Clave
| Aspecto | SPA Clásica | SSR Tradicional (Node.js) | Edge Bot-Proxy (Cloudflare) |
| Tarjetas en WhatsApp / Redes | ❌ Genéricas / Vacías | ✅ Dinámicas | ✅ Dinámicas con foto oficial |
| Indexación en Google / Bots IA | ⚠️ Depende de ejecución JS | ✅ Completa | ✅ Completa con Schema.org y HTML semántico |
| Coste de Servidores | $0 (Estático) | $$ (VPS / Cloud Run) | $0 (Capa gratuita Cloudflare) |
| Complejidad de Código | Mínima | Alta (Hidratación, Server Mocks) | Aislada en 1 función Edge |
| Velocidad para usuarios reales | Instantánea (CDN) | Depende del render del servidor | Instantánea (Puro CDN Edge) |
6. Conclusión
No siempre necesitas rehacer tu aplicación para añadir un framework SSR complejo solo para tener buen SEO y compartir enlaces con tarjetas visuales atractivas.
Con el patrón Edge Bot-Proxy sobre Cloudflare Pages:
- Mantienes la simplicidad y agilidad de tu SPA.
- Aprovechas la red de baja latencia de Cloudflare en todo el mundo.
- Tus usuarios disfrutan de una experiencia PWA nativa mientras los rastreadores de WhatsApp, Twitter y Google reciben exactamente el HTML y los metadatos que esperan.