El renderizado del lado del servidor en servidores origin tradicionales significa latencia de 200-400ms en promedio. Si cacheas HTML en un edge de CDN, esa cifra baja a 20-50ms, pero pierdes personalización. Edge SSR rompe este tradeoff: obtienes tanto personalización como response por debajo de 40ms. Lo logras usando runtimes edge como Cloudflare Workers y Vercel Edge, combinados con KV distribuido. Ya no te haces la pregunta "¿cache o personalización?" — obtienes ambas.
Por qué Edge SSR es crítico ahora
Desde 2025, la métrica INP de Chrome forma parte de Core Web Vitals. Un response del servidor por encima de 200ms es suficiente para romper INP por sí solo. Cada request al origin suma 150-300ms porque implica distancia física y cold start. El runtime edge elimina este cuello de botella: tu código corre en el POP (Punto de Presencia) más cercano al usuario, y los datos se recuperan del KV store regional en 5-15ms.
No es solo velocidad. Para personalización, ya no necesitas golpear el origin a cada request. Guardas en KV edge el segmento de usuario, preferencias, estado del carrito. Cuando llega un request, la función edge extrae esos datos y renderiza el HTML al instante. El servidor origin solo maneja escrituras y computation pesada.
Cuando trabajas con plataformas como Shopify, esta arquitectura es especialmente valiosa. Las templates Liquid renderizadas en origin toman 300-600ms por página. Con Edge SSR, compones HTML de forma declarativa: una función edge renderiza la tarjeta de producto, otra inyecta la información del carrito. La latencia total cae por debajo de 40ms. Para integración detallada, consulta Headless Commerce.
Cloudflare Workers + KV: núcleo arquitectónico
Cloudflare Workers usa aislamiento V8. No crea un nuevo contenedor para cada request, sino que abre un aislado JavaScript. Ese costo es de apenas 0.5-2ms. El código del Worker se ve así:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = request.headers.get('CF-Connecting-IP') || 'anonymous';
// Obtén el segmento del usuario desde KV
const segment = await env.USER_SEGMENTS.get(userId);
// Renderiza lista de productos según el segmento
const products = segment === 'premium'
? await fetchPremiumProducts()
: await fetchStandardProducts();
const html = renderHTML(products, segment);
return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' }
});
}
};
Cloudflare KV se replica en más de 300 POPs. La latencia de lectura promedia globalmente 12ms. Las escrituras se propagan con consistencia eventual en unos 60 segundos. Por eso solo escribes en KV datos que cambian raramente: preferencias del usuario, mapeos de segmentos, feature flags. Datos que cambian frecuentemente, como el precio del producto, los buscas en la API del origin y los cacheas en edge con TTL de 60 segundos usando Cache API.
Vercel Edge vs Cloudflare Workers
Las Vercel Edge Functions usan el mismo modelo de aislamiento V8, pero su red es diferente. Cloudflare tiene 300+ POPs, Vercel tiene ~15 ubicaciones edge regionales. Comparativa de latencia (usuario en Europa, origin en EE.UU.):
| Runtime | Cold Start | KV Read | TTFB Total |
|---|---|---|---|
| Origin SSR | 150ms | N/A | 380ms |
| Vercel Edge | 8ms | 22ms | 45ms |
| Cloudflare Workers | 1ms | 11ms | 28ms |
La ventaja de Vercel es integración profunda con el ecosistema Next.js. Escribes una función edge en middleware.ts y la despliegas a producción; Vercel maneja la orquestación. En Cloudflare necesitas CLI de Wrangler y binding manual de KV. Tradeoff: más control versus incorporación más rápida.
Arquitectura KV: patrón de escritura y revalidación
La consistencia eventual de KV edge es una restricción. Un usuario hace clic en un botón, su preferencia cambia — ese cambio se propaga a todos los edges en 60 segundos. En ese período, diferentes POPs pueden leer valores distintos. Solución: redirige al origin después de escribir, o usa actualización optimista en el cliente.
Flujo de ejemplo:
- El usuario presiona el toggle de "Modo Oscuro"
- El cliente envía POST a
/api/preferences(origin) - El origin escribe
user:123:theme = darken KV - El origin llama a la API de Cloudflare para invalidar cache:
// En el origin
await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiToken}` },
body: JSON.stringify({ files: [`https://example.com/user/${userId}`] })
});
- La función edge lee el nuevo valor desde KV en el próximo request
- JavaScript del cliente realiza un soft reload 200ms después
Este patrón limita el throughput de escritura (límite de KV: 1000/segundo por cuenta), pero el throughput de lectura es ilimitado. La arquitectura se optimiza para workloads de lectura intensiva. Las acciones del usuario son infrecuentes (1-2 por minuto), pero las vistas de página son frecuentes (100+ por segundo).
Estrategia de capas de caché
KV no es la única capa de caché. Stack completo:
Browser Cache (service worker)
↓
CDN Edge Cache (Cache API, TTL 60s)
↓
Edge KV (eventual, minutos)
↓
Base de datos del Origin
Los assets estáticos (CSS, JS) van en la capa superior; datos específicos del usuario, en la inferior. El HTML se compone en la capa media: la función edge combina KV + Cache API para renderizar. Pseudocódigo:
const cacheKey = `html:${url}:${segment}`;
let html = await caches.default.match(cacheKey);
if (!html) {
const userData = await KV.get(userId);
html = renderTemplate(userData);
await caches.default.put(cacheKey, html, { expirationTtl: 60 });
}
return html;
Esta estructura mantiene TTFB en el percentil 95 por debajo de 40ms porque la mayoría de requests se sirven desde Cache API (5-8ms). La tasa de acierto de KV es >98%, fallback al origin <2%.
Scope de personalización y tradeoff de tamaño de bundle
Las funciones edge tienen límite de 1MB de tamaño de bundle (Cloudflare). No puedes renderizar componentes React pesados. Dos estrategias:
1. Templating mínimo: usa Handlebars o interpolación de string personalizada. Solo inyecta variables:
const template = `<div class="product-card">
<h3>{{name}}</h3>
<span class="price {{priceClass}}">{{price}}</span>
</div>`;
function render(product, segment) {
return template
.replace('{{name}}', product.name)
.replace('{{price}}', segment === 'premium' ? product.premiumPrice : product.price)
.replace('{{priceClass}}', segment === 'premium' ? 'gold' : 'standard');
}
Tamaño de bundle: 2KB. Tiempo de renderizado: 0.3ms.
2. Hydration parcial: renderiza esqueleto de HTML en edge, hidrata islas React en el cliente. Función edge:
export default async function(request) {
const products = await fetchProducts();
return `
<div id="product-list" data-products='${JSON.stringify(products)}'>
${products.map(p => `<div class="skeleton"></div>`).join('')}
</div>
<script type="module" src="/hydrate.js"></script>
`;
}
Cliente-side hydrate.js (10KB):
import { h, render } from 'preact';
const data = JSON.parse(document.getElementById('product-list').dataset.products);
render(<ProductList products={data} />, document.getElementById('product-list'));
Este patrón mantiene baja la latencia de Edge SSR (40ms), mientras que la interactividad llega del cliente (FCP + 150ms). Tradeoff: INP puede aumentar (tiempo de parsing de JavaScript). Se requiere monitoreo.
Monitoreo de usuario real y alerting
No puedes optimizar latencia edge sin RUM. Cloudflare Analytics agrega un header server-timing a cada request:
Server-Timing: cf-edge;dur=12, cf-kv;dur=8, cf-render;dur=18
Recolecta esto con PerformanceObserver en el cliente:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'navigation') {
const ttfb = entry.responseStart - entry.requestStart;
fetch('/analytics', {
method: 'POST',
body: JSON.stringify({ ttfb, url: entry.name })
});
}
}
}).observe({ entryTypes: ['navigation'] });
Métricas objetivo:
- p50 TTFB < 30ms
- p95 TTFB < 60ms
- p99 TTFB < 100ms
- Tasa de error de edge < 0.1%
Si el TTFB supera 60ms, registra el ID de traza de Cloudflare y depura con Wrangler tail. La mayoría de veces el culpable es timeout de KV o fallback al origin.
Checklist de despliegue en producción
Antes de llevar Edge SSR a producción:
- Rate limiting: throttle escrituras en KV (1 write por segundo por usuario)
- Cadena de fallback: si KV timeout (>50ms), fallback al origin; si origin timeout, sirve HTML estático
- Feature flag: activa personalización edge de forma gradual (10% → 50% → 100% de traffic)
- Monitoreo de costos: Cloudflare Workers ofrece 100K requests/día gratis, después $0.50 por millón. KV read es ilimitado y gratis, writes cuestan $0.50 por millón.
- Seguridad: hashea IDs de usuario, no guardes PII en claves de KV, añade detección de bots para bypass de rate limit
Proyección de costos: 1M visitas diarias, 30% de requests personalizados = 300K invocaciones de edge/día = $0.15/día = $4.50/mes. La alternativa (Origin SSR): instancia de 2 vCPU a $50/mes. Ahorro: 91%.
Una vez que Edge SSR está en marcha, el costo incremental es cero. Añadir una regla de personalización nueva es simplemente escribir una clave nueva en KV. Crear un segmento nuevo es agregar un if en la función edge. El escalado no es lineal sino logarítmico — 10M requests/día se sirven con la misma latencia de 40ms. Por eso pensar en arquitectura edge-first desde el inicio proporciona ventaja fundamental en estrategia de crecimiento.