En tiendas de comercio electrónico modernas, la personalización ya no es un diferenciador — es una expectativa. Pero los usuarios no quieren esperar 250ms cada vez que hacen clic. La arquitectura SSR tradicional (server-side rendering) introduce latencia de 150-300ms en promedio entre usuario y servidor origen: búsqueda DNS, handshake TCP, negociación TLS, tiempo de procesamiento en origen. Edge SSR reduce este retraso a 40-60ms combinando proximidad geográfica y almacenamiento KV global. Plataformas como Cloudflare Workers y Vercel Edge Functions ofrecen runtimes edge — nuestro trabajo es trasladar la lógica de personalización allí y construir el almacén KV correctamente.
Diferencia de latencia: Edge SSR vs. SSR de origen
En SSR tradicional, la solicitud sigue esta ruta: usuario → CDN (miss de caché) → servidor origen (consulta de DB + rendering) → respuesta. Tiempo total promedio: 250ms, percentil 95: 450ms. En Edge SSR, la solicitud termina en la ubicación edge: usuario → worker edge (búsqueda KV + rendering) → respuesta. Promedio: 40ms, percentil 95: 80ms.
Fuentes de latencia:
| Paso | SSR de origen | Edge SSR |
|---|---|---|
| DNS + TLS | 50ms | 15ms (proximidad edge) |
| Network RTT | 120ms (intercontinental) | 10ms (distancia a edge) |
| Compute | 80ms (origen) | 15ms (V8 isolate) |
| Total | 250ms | 40ms |
Esta reducción del 84% afecta directamente a LCP (Largest Contentful Paint) y CLS (Cumulative Layout Shift). Según el informe Core Web Vitals 2025 de Google, cada 100ms de mejora en LCP genera un aumento del 3.5% en tasa de rebote — ganar 210ms significa una mejora de conversión del 7.3% (cálculo: 210/100 × 3.5).
Tradeoff: el runtime edge no es Node.js sino un V8 isolate — no puedes usar módulos nativos, sistema de archivos ni procesos secundarios. La lógica de personalización debe ser completamente stateless y ligera.
Arquitectura Edge SSR con Cloudflare Workers
Cloudflare Workers enruta cada solicitud a una de las 300+ ubicaciones edge en su red global. La solicitud se procesa en edge de esta forma:
// worker.js — Cloudflare Workers
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = request.headers.get('x-user-id'); // parseado desde JWT
// Obtener segmento de usuario de KV
const segment = await env.USER_SEGMENTS.get(userId);
const prefs = segment ? JSON.parse(segment) : { tier: 'free' };
// Renderizar HTML personalizado
const html = renderHTML(prefs, url.pathname);
return new Response(html, {
headers: {
'content-type': 'text/html;charset=UTF-8',
'cache-control': 'public, s-maxage=60', // caché edge 60s
},
});
},
};
function renderHTML(prefs, path) {
const hero = prefs.tier === 'premium'
? '<h1>Contenido Premium</h1>'
: '<h1>Contenido Gratuito</h1>';
return `<!DOCTYPE html><html><body>${hero}<p>Ruta: ${path}</p></body></html>`;
}
Este código obtiene el segmento de usuario de KV en cada solicitud desde el namespace USER_SEGMENTS. La latencia de lectura de KV promedia 15ms globalmente (benchmark Cloudflare 2025). Como alternativa, puedes usar Durable Objects, pero para cargas de trabajo heavy-read, KV es más económico (KV: $0.50 por millón de lecturas, DO: $0.15 por millón de solicitudes + compute).
El límite de tiempo CPU en Workers es 50ms — con rendering complejo puedes excederlo. La solución: pre-renderizar plantillas como HTML en KV, el worker solo hace reemplazos de cadenas. Por ejemplo, el worker reemplaza el placeholder {USER_NAME} mientras la plantilla vive en KV.
Integración Vercel Edge Functions con Next.js Middleware
Vercel Edge Functions integra nativamente con Next.js 13+ — puedes interceptar solicitudes y personalizarlas usando el patrón middleware. En lugar de getServerSideProps, usas middleware.ts:
// middleware.ts — Vercel Edge
import { NextRequest, NextResponse } from 'next/server';
export async function middleware(req: NextRequest) {
const userId = req.cookies.get('user_id')?.value;
if (!userId) return NextResponse.next();
// Obtener segmento de Edge Config (KV de Vercel)
const segment = await fetch(`https://edge-config.vercel.com/${userId}`).then(r => r.json());
// Agregar información de segmento a header, component de página lo lee
const response = NextResponse.next();
response.headers.set('x-user-segment', segment.tier);
return response;
}
export const config = {
matcher: ['/product/:path*', '/category/:path*'],
};
Este enfoque funciona bien para personalizaçar páginas de listado de productos en arquitecturas headless commerce. Por ejemplo, muestras a usuarios premium un ordenamiento de productos diferente. El componente page lo lee así:
// app/product/[id]/page.tsx
export default async function ProductPage({ params, headers }) {
const segment = headers.get('x-user-segment');
const products = await fetchProducts(params.id, segment);
return <ProductList items={products} />;
}
Vercel Edge Config replica globalmente en 150ms — las actualizaciones de almacenamiento se propagan a los edges en ese tiempo. Tradeoff: Vercel Edge Config es 20% más lento que Cloudflare KV pero integra mejor con el ecosistema Next.js.
Arquitectura KV Store: Estrategia de Segmentación
Los datos de personalización viven en KV en 3 capas:
- Segmento de usuario:
USER_SEGMENTS:{userId}→{"tier":"premium","region":"EU"} - Config de segmento:
SEGMENT_CONFIG:{tier}→{"discount":0.2,"hero":"premium.jpg"} - Plantilla de página:
PAGE_TPL:{page}:{tier}→ fragmento HTML pre-renderizado
Esta estructura significa que cuando cambia el segmento, solo USER_SEGMENTS se actualiza — las plantillas permanecen en caché. Para 1 millón de usuarios, el costo de KV es: 1M usuarios × 1 lectura/solicitud × $0.50 por millón de lecturas = $0.0000005 por solicitud. Una consulta de DB de origen cuesta 100 veces más.
Estrategia TTL en KV:
// Segmento en caché 24 horas
await env.USER_SEGMENTS.put(userId, JSON.stringify(segment), {
expirationTtl: 86400,
});
// Config en caché 1 hora (puede cambiar frecuentemente)
await env.SEGMENT_CONFIG.put(tier, JSON.stringify(config), {
expirationTtl: 3600,
});
Invalidación: cuando un usuario se actualiza (upgrade), envías una señal vía WebSocket o webhook al worker, que actualiza KV. Pero no es en tiempo real — acepta consistencia eventual (1-5 minutos de lag).
Tradeoffs de rendering: Static vs. Edge SSR
Edge SSR no siempre es la mejor solución. Comparación:
| Métrica | Static (ISR) | Edge SSR | SSR de origen |
|---|---|---|---|
| TTFB | 20ms | 40ms | 250ms |
| Personalización | No | Sí | Sí |
| Hit ratio de caché | 99% | 60% | 10% |
| Costo (1M solicitudes) | $0.20 | $2.50 | $15 |
| Complejidad | Baja | Media | Alta |
ISR logra 99% hit ratio pero sin personalización. Edge SSR fragmenta el caché por segmento de usuario — cada segmento crea una clave de caché separada, reduciendo el ratio de hits.
Enfoque híbrido: layout estático, componentes personalizados en edge renderizados e inyectados side-by-side. Por ejemplo, grid de productos estático, "Recomendado para ti" vía Edge SSR:
// Híbrido: HTML estático + sección personalizada inyectada en edge
const staticHTML = await env.STATIC_PAGES.get(pathname);
const personalizedSection = await renderPersonalizedRecommendations(userId);
const finalHTML = staticHTML.replace('<!--INJECT-->', personalizedSection);
Este enfoque mantiene TTFB en 30ms mientras entrega personalización.
Debugging y monitoreo: Límites del runtime edge
Debuggear en edge runtime en production es difícil — logs dispersos, stack traces incompletos. Con Cloudflare Workers, puedes usar Tail Workers para crear un stream de logs en tiempo real:
// tail-worker.js
export default {
async tail(events) {
for (const event of events) {
console.log(JSON.stringify({
timestamp: event.timestamp,
outcome: event.outcome,
logs: event.logs,
}));
}
},
};
En Vercel, console.log va a edge logs, streamable desde el dashboard. Pero verbose logging en production puede exceder el límite de CPU — registra solo eventos críticos.
Métricas de monitoreo:
- Cold start latency: El worker inicialmente tarda 80-120ms en cargar — solicitudes warm tardan 15ms. Las rutas frecuentes permanecen warm.
- KV read failure rate: 0.01% (SLA Cloudflare). Fallback: si KV falla, usa segmento por defecto.
- CPU time: Exceder 50ms retorna error %429. Profiling: mide con
console.time(), traslada operaciones pesadas a origen.
Manejo de errores ejemplo:
try {
const segment = await env.USER_SEGMENTS.get(userId);
} catch (err) {
// KV falló — fallback a defecto
return renderHTML({ tier: 'free' }, pathname);
}
Si aceptas estos tradeoffs de Edge SSR, la caída de 250ms a 40ms genera diferencias medibles en conversión. Especialmente crítico para usuarios móviles donde la latencia de red es alta. El siguiente paso es diseñar el almacén KV correctamente, definir tu estrategia de segmentación y testear los límites del runtime edge.