1. Por qué pensar en caché y edge en una ChatGPT App
En una aplicación web clásica también te preocupas por la velocidad, pero allí al menos el usuario ve un spinner. En una ChatGPT App la situación es más interesante. El usuario conversa con el modelo y este a veces decide invocar tu App. El widget debe aparecer y mostrar algo útil con bastante rapidez.
La práctica es clara: latency = dinero. Cuanto más tardes en responder, mayor la probabilidad de que el usuario se marche, y las llamadas extra a la LLM/back‑end son costes directos de modelos e infraestructura. La caché reduce ambas cosas.
Además, las ChatGPT Apps tienen particularidades:
- Las solicitudes desde ChatGPT hacia tu App recorren la red y varias capas. Cada milisegundo en cada paso se acumula.
- Los endpoints MCP/HTTP tienen timeouts reales (incluidas las funciones serverless y edge en Vercel). Si no llegas a tiempo, ChatGPT ve un error e incluso puede empezar a «alucinar» la respuesta.
- Muchos datos en GiftGenius no cambian cada segundo: la estructura del catálogo de regalos, selecciones de «ideas top» para distintos segmentos, ajustes de features. Es absurdo golpear la base de datos o una API externa cada vez.
Y aquí entran en juego:
- CDN y edge cache, para servir rápido estáticos y JSON cacheable.
- Caché HTTP con Cache-Control/ETag/SWR, para acelerar y abaratar las solicitudes repetidas.
- Funciones edge de Vercel, para ejecutar lógica ligera lo más cerca posible de ChatGPT y del usuario, pero sin convertirlas en «mini‑backend».
2. Anatomía de la latencia en GiftGenius y puntos de caché
Primero conviene dibujar con honestidad dónde nace la latencia.
sequenceDiagram
participant User as Usuario
participant ChatGPT as ChatGPT
participant App as ChatGPT App (Apps SDK)
participant GW as MCP Gateway / Edge
participant GiftAPI as Gift REST API / microservicio de regalos
participant DB as Catálogo/Base de datos
User->>ChatGPT: "Elige un regalo para mi hermano"
ChatGPT->>App: Invocación de herramienta + render del widget
App->>GW: Solicitud HTTP/MCP (categorías, selecciones)
GW->>GiftAPI: HTTP (REST)
GiftAPI->>DB: Consulta de catálogo/recomendaciones
DB-->>GiftAPI: Respuesta
GiftAPI-->>GW: Respuesta (JSON)
GW-->>App: Respuesta (JSON)
App-->>ChatGPT: Widget con resultados
ChatGPT-->>User: Mensaje + UI
¿Dónde se puede «recortar» aquí?
- Entre ChatGPT y tu perímetro — CDN/edge cache (Vercel CDN/Edge Network), que puede servir activos inmutables del widget y JSON cacheable sin tocar tu servidor de origen.
- Entre el Gateway y los servicios REST/HTTP internos (Gift REST API, Commerce REST API, etc.) y la base — caché de aplicación (Redis/en memoria/caché en BD), para no repetir las mismas consultas (por ejemplo, «lista de categorías de regalos») diez veces.
En esta lección nos centramos en la capa HTTP/edge, porque está más cerca de ChatGPT y Vercel.
3. Tipos de caché en nuestra arquitectura
Como nuestra arquitectura es «en capas», hay varios tipos de caché.
| Tipo de caché | Dónde vive | Para qué sirve |
|---|---|---|
| Caché del navegador | Dentro del cliente de ChatGPT (navegador/desktop) | Estáticos del widget, iconos, fuentes (control limitado) |
| CDN / edge cache | En nodos edge de Vercel/Cloudflare | Estáticos + JSON común (categorías, configuraciones, selecciones generales) |
| Caché de aplicación | Dentro de tu MCP Gateway o servicios backend (Redis, en memoria) | Resultados de consultas pesadas a BD/APIs externas |
| Caché/Materialización en BD | En la propia BD (vistas materializadas, etc.) | Agregados precalculados, analítica |
Ahora nos centramos en los dos primeros: caché HTTP + CDN/edge.
4. Caché HTTP: Cache-Control, max-age y s-maxage
La caché HTTP se controla principalmente con el encabezado Cache-Control. De él depende si el navegador/cliente de ChatGPT y/o el CDN pueden cachear tu respuesta y durante cuánto tiempo.
Puntos clave:
- max-age — cuántos segundos puede cachear la respuesta el navegador.
- s-maxage — cuántos segundos puede cachearla el shared cache (CDN/proxy).
- public — la respuesta puede cachearse en caché compartida.
- private — respuesta solo para un cliente concreto; el CDN no la cachea.
En GiftGenius, por ejemplo:
- JS/CSS/fuentes del widget — archivos versionados (con hash en el nombre); se pueden servir con Cache-Control: max-age=31536000, immutable.
- El JSON con la lista de categorías de regalos — es igual para todos los usuarios; aquí tiene sentido public, s-maxage=60 (o más).
Un Route Handler de Next.js sencillo para GET /api/gifts/categories, cacheado en el CDN durante 60 segundos:
// app/api/gifts/categories/route.ts
import { NextResponse } from "next/server";
export const runtime = "nodejs"; // función serverless normal
export async function GET() {
// aquí podríamos consultar una BD/API externa
const categories = [
{ id: "for_brother", title: "Regalos para hermano" },
{ id: "for_mom", title: "Regalos para mamá" },
];
return NextResponse.json(categories, {
headers: {
// permitimos que el CDN lo cachee 60 segundos
"Cache-Control": "public, s-maxage=60",
},
});
}
El CDN de Vercel almacenará la respuesta 60 segundos, y todas las solicitudes de ChatGPT por ese JSON dentro de esa ventana ni siquiera alcanzarán tu función. Es instantáneo y barato.
5. ETag: huella del contenido y 304 Not Modified
ETag es una «huella» del recurso, normalmente un hash del contenido. Flujo de trabajo:
- El servidor responde con el encabezado ETag: "v1-abc123".
- La próxima vez el cliente envía el encabezado If-None-Match: "v1-abc123".
- Si el servidor considera que el contenido no cambió, responde con 304 Not Modified sin cuerpo.
Importante: ETag ahorra tráfico, pero no necesariamente reduce la latencia, porque sigue haciendo falta un viaje de ida y vuelta al servidor. En el contexto de ChatGPT Apps es útil para respuestas JSON pesadas, pero no esperes milagros solo con ETag: para velocidad es mejor SWR y caché en edge.
Ejemplo de ETag simple en un handler de Next.js (sin hashes cripto para no complicar):
// app/api/gifts/config/route.ts
import { NextRequest, NextResponse } from "next/server";
const CONFIG = { version: 1, showExperimentalIdeas: true };
const ETAG = `"v${CONFIG.version}"`;
export async function GET(req: NextRequest) {
const ifNoneMatch = req.headers.get("if-none-match");
if (ifNoneMatch === ETAG) {
// El contenido no ha cambiado: devolvemos 304
return new NextResponse(null, { status: 304, headers: { ETag: ETAG } });
}
return NextResponse.json(CONFIG, {
headers: {
ETag: ETAG,
"Cache-Control": "public, s-maxage=300",
},
});
}
En producción, calcularás el ETag a partir del hash de los datos o usarás la versión del registro en la BD.
6. Stale‑While‑Revalidate (SWR): rápido y suficientemente fresco
SWR es el enfoque «muestra lo antiguo de inmediato y actualiza en segundo plano». Puede implementarse:
- A nivel de encabezado HTTP Cache-Control con el parámetro stale-while-revalidate.
- A nivel de UI, usando bibliotecas como swr/react-query, que mantienen caché local y hacen refetch en segundo plano.
SWR en el encabezado HTTP
Encabezado típico:
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
Significado:
- Durante los primeros 60 segundos el CDN sirve la versión fresca.
- Desde el segundo 61 hasta el 360 el CDN puede devolver una respuesta obsoleta al instante, y lanzar en segundo plano una petición al origin para la nueva versión.
- Tras 360 segundos, la solicitud del contenido nuevo pasa a ser bloqueante.
El usuario (y ChatGPT) recibe la respuesta al instante incluso en picos de carga, mientras actualizas la caché en segundo plano. Para GiftGenius es ideal, por ejemplo, para «selecciones top de regalos para Año Nuevo» — no cambian cada segundo.
Ejemplo:
// app/api/gifts/top/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const topGifts = [
{ id: "coffee_mug", title: "Taza con inscripción" },
{ id: "smart_led", title: "Lámpara inteligente" },
];
return NextResponse.json(topGifts, {
headers: {
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
},
});
}
SWR en el widget de UI (React)
El widget de GiftGenius vive en la sandbox de ChatGPT y puede usar cualquier código React. Ya sabes llamar a tu API mediante window.fetch. Añadamos la biblioteca swr y organicemos caché en el lado del widget:
// widget/GiftTopList.tsx
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export function GiftTopList() {
const { data, isLoading } = useSWR(
"https://api.giftgenius.com/api/gifts/top",
fetcher,
{ revalidateOnFocus: false } // en el chat el foco cambia de forma extraña; lo desactivamos
);
if (isLoading && !data) return <div>Cargando ideas...</div>;
return (
<ul>
{data?.map((gift: any) => (
<li key={gift.id}>{gift.title}</li>
))}
</ul>
);
}
Cómo funciona:
- En el primer render se hace una petición a nuestra API.
- El resultado se guarda en la caché swr dentro del widget.
- En renders posteriores (o nuevas respuestas donde ChatGPT inserta de nuevo este widget con la misma clave) los datos se toman de la caché. El usuario no ve parpadeos ni spinners, y puede lanzarse una actualización en segundo plano.
Así combinamos dos niveles de SWR:
- En CDN/HTTP — para no cargar el origin.
- En la UI — para no cargar al usuario.
Si lo juntamos todo:
- Un Cache-Control sencillo (max-age/s-maxage) — capa base: otorgamos a CDN y clientes permiso para cachear y reducir carga.
- ETag + If-None-Match — añadir cuando importa ahorrar tráfico para JSON pesados, aceptando el viaje de red.
- stale-while-revalidate — activarlo cuando importa entregar al instante incluso datos algo desactualizados (catálogos, selecciones top).
- SWR en la UI (biblioteca swr/react-query) — capa aparte para suavizar re-renderizados del widget y caché local en la sandbox de ChatGPT.
7. Qué cachear en GiftGenius y durante cuánto
Clasifiquemos los datos de GiftGenius por «capas de cacheabilidad».
Se puede cachear a nivel de CDN/edge
Todo lo que es igual para todos (o para segmentos amplios) y cambia raramente:
- Estáticos del widget: JS/CSS, fuentes, iconos — prácticamente «para siempre» (un año) con immutable.
- Estructura de catálogos de regalos: categorías, secciones, filtros — minutos/horas.
- Selecciones generales («mejores ideas para colegas hasta 50 $») — minutos/decenas de minutos, especialmente en temporadas pico.
Aquí encaja perfectamente public, s-maxage + stale-while-revalidate.
Mejor cachear en la aplicación/Redis
Más dinámicos, pero repetitivos:
- Resultados de APIs externas pesadas (por ejemplo, tipos de cambio, precios actuales de una tienda externa).
- Segmentos de recomendaciones muy consultados (por sexo/edad/ocasión).
Aquí el CDN no siempre sirve, porque los datos pueden depender del token/organización/tenant. Cachea en el MCP Gateway o en servicios REST internos: está bajo tu control y no mezcla datos de usuarios.
No cachear (en cachés compartidas)
Lo ligado a un usuario concreto:
- Pedidos personales y estados de pedido.
- Información de pago, direcciones, email.
- Recomendaciones concretas basadas en historial privado de pedidos (si es sensible).
Esto solo se puede cachear a nivel de aplicación con semántica cuidadosa (y obligatoriamente sin fugas entre usuarios), pero desde luego no en la caché public del CDN.
8. Capa edge: CDN frente a funciones edge
Es importante no confundir dos cosas parecidas pero distintas:
- CDN / edge cache — almacena respuestas precomputadas; casi no hay lógica.
- Funciones edge (Vercel Edge / Cloudflare Workers) — pequeños trozos de código que se ejecutan en nodos edge.
La experiencia lo demuestra: Edge ≠ Serverless. Muchos desarrolladores intentan meter ahí lógica de negocio pesada, llamadas a LLM y procesamiento de BLOBs, y luego se sorprenden por timeouts y límites. Las funciones edge:
- Arrancan muy rápido (cold start casi nulo).
- Pero están muy limitadas en CPU, tiempo de ejecución y APIs disponibles (a menudo sin Node.js completo, sin sockets largos, etc.).
Cuándo una función edge es buena idea
En el contexto de GiftGenius y la ChatGPT App, son útiles para:
- Enrutamiento ligero: según encabezados locale, x-openai-user-location o tenant ID decidir a qué clúster backend regional enviar la solicitud.
- Añadir encabezados simples, feature flags, enrutamiento A/B.
- Endpoints read-only rápidos que leen datos de una edge‑KV o del caché del CDN y apenas computan nada.
Cuándo una función edge es mala idea
- Peticiones largas a APIs externas.
- Invocaciones a modelos LLM.
- Lógica de checkout compleja.
- Herramientas MCP con lógica de negocio pesada.
Para todo eso tienes las funciones serverless normales de Next.js (por ejemplo, runtime = "nodejs") o servicios/clústeres aparte.
Ejemplo de función edge en Next.js 16
Hagamos una pequeña ruta GET /api/geo-router, que a partir del encabezado x-openai-user-location (hipotético) devuelva a qué clúster regional llamar.
// app/api/geo-router/route.ts
import { NextRequest, NextResponse } from "next/server";
export const runtime = "edge"; // ejecutamos en edge
export function GET(req: NextRequest) {
const userLocation = req.headers.get("x-openai-user-location") ?? "US";
const cluster =
userLocation.startsWith("EU") ? "eu-gift-api" : "us-gift-api";
return NextResponse.json({ cluster }, {
headers: {
"Cache-Control": "public, s-maxage=300",
},
});
}
Un endpoint así:
- Funciona muy rápido (edge).
- No hace nada complejo.
- Puede cachearse en el CDN.
9. Edge y caché en la arquitectura global de GiftGenius
Juntémoslo todo en un diagrama.
flowchart TD
ChatGPT[(ChatGPT / Usuario)]
CDN["CDN / Edge Cache (Vercel)"]
EdgeFn["Edge Functions (enrutamiento, feature flags)"]
GW[MCP Gateway]
GiftAPI["Gift REST API Cluster"]
CommerceAPI["Commerce REST API Cluster"]
DB[(BD/APIs externas)]
ChatGPT --> CDN
CDN -->|cache hit| ChatGPT
CDN -->|cache miss| EdgeFn
EdgeFn --> GW
GW --> GiftAPI
GW --> CommerceAPI
GiftAPI --> DB
CommerceAPI --> DB
Escenario típico:
- El widget de ChatGPT solicita /api/gifts/categories.
- El CDN comprueba la caché. Si hay una versión fresca o «stale pero válida», la sirve al instante, sin tocar EdgeFn/GW.
- Si no hay caché — la solicitud cae en EdgeFn (si está activado) y/o directamente en GW.
- GW, si es necesario, usa una caché interna de Redis para operaciones pesadas o llama a servicios REST internos y luego a la BD.
- La respuesta vuelve, entra en el caché del CDN/edge y se sirve a otros usuarios.
Esta arquitectura:
- Reduce la latencia para el widget y ChatGPT.
- Disminuye la carga sobre el MCP Gateway y los clústeres backend.
- Reduce el coste de llamadas a LLM/BD (menos repeticiones).
10. Pequeños fragmentos prácticos para GiftGenius
Caché de categorías + Next.js revalidate
Hasta ahora hablamos solo de endpoints de API. Pero Next.js ofrece mecanismos similares para páginas — mediante ISR (revalidate).
Ejemplo de server component que obtiene la lista de categorías con revalidate = 60:
// app/(widget)/categories/page.tsx
export const revalidate = 60; // ISR: reconstruir cada 60 s
async function fetchCategories() {
const res = await fetch("https://api.giftgenius.com/api/gifts/categories");
return res.json();
}
export default async function CategoriesPage() {
const categories = await fetchCategories();
return (
<ul>
{categories.map((c: any) => (
<li key={c.id}>{c.title}</li>
))}
</ul>
);
}
En producción, Vercel generará y cacheará la salida HTML de esta página, útil cuando tu widget/interfaz también se abre fuera de ChatGPT como página web normal (p. ej., panel de depuración o landing).
Aplicación de caché sencilla en un servicio backend
Esto ya no es la capa edge, sino caché de aplicación (Redis/en memoria dentro de tu Gift REST API u otro servicio backend). Pero viene al caso mostrar cómo se ve en su forma más simple:
// pseudo‑código dentro del Gift REST API
const cache = new Map<string, any>();
async function getGiftCategories() {
const key = "gift_categories_v1";
const cached = cache.get(key);
if (cached && Date.now() - cached.ts < 60_000) {
return cached.data; // caché de 60 s
}
const data = await fetchRealCategories();
cache.set(key, { ts: Date.now(), data });
return data;
}
En producción, por supuesto, sustituirás Map por Redis/Memcached, pero la idea es la misma: menos viajes a la BD/API externa.
Si resumimos todo en una frase: primero decide con claridad qué se puede cachear y dónde (CDN, edge, Redis, BD), y solo después activa los «flags mágicos» de la plataforma. La caché no es una casilla de configuración, es parte de la arquitectura: afecta a la velocidad, la estabilidad y el coste.
11. Errores habituales al trabajar con caché y la capa edge
Error n.º 1: «Cachear todo sin distinción, con tal de ir más rápido».
Clásico: el desarrollador pone Cache-Control: public, s-maxage=3600 en todas las respuestas JSON. A las pocas horas resulta que un usuario ve los pedidos de otro, y ChatGPT empieza a operar con datos antiguos de disponibilidad. Para datos personales o sensibles necesitas o bien caché private, o desactivar por completo la caché del CDN y mantenerla a nivel de aplicación con aislamiento cuidadoso.
Error n.º 2: Confusión entre max-age y s-maxage.
Algunos configuran solo max-age y esperan que el CDN cachee exactamente lo mismo. En realidad, max-age se aplica ante todo al navegador, y para el caché compartido se necesita s-maxage. Resultado: el navegador cachea, pero el CDN no, y el origin sigue ahogándose bajo la carga, aunque «pusimos caché». La vía correcta es indicar explícitamente s-maxage para el CDN.
Error n.º 3: Esperar que ETag lo acelere todo.
ETag ahorra perfectamente tráfico, sobre todo para JSON grandes, pero el viaje de red sigue ahí. En el mundo de la ChatGPT App esto significa: el modelo igualmente espera una respuesta de tu servidor, aunque sea 304 sin cuerpo. Si lo que te importa es la latencia, necesitas caché en edge + SWR, y ETag es un mecanismo auxiliar.
Error n.º 4: Intentar meter lógica de negocio pesada en funciones edge.
«Llamemos a una LLM externa, calculemos selecciones complejas y consultemos tres APIs externas desde Vercel Edge — ¡es rápido!» Luego llega el dolor: límites de tiempo de ejecución, ausencia de Node.js completo, errores extraños. Edge es bueno para enrutamiento ligero y A/B; todo lo pesado debe ir a funciones serverless normales o a clústeres backend separados.
Error n.º 5: Falta de estrategia de invalidación de caché.
Pusiste caché «de una hora», todo vuela. Luego negocio dice: «hemos cambiado precios/categorías/restricciones, ¿por qué en ChatGPT sigue todo como antes?» Los desarrolladores empiezan a tocar palancas a mano, limpiar cachés y reiniciar servicios. Para datos importantes, planifica de antemano cómo vas a limpiar la caché (por webhook desde la admin, por versión, por clave), y no confíes en «ya se actualizará sola en una hora».
Error n.º 6: Ignorar la relación caché ↔ coste.
A veces se piensa en la caché solo como velocidad. En el ecosistema LLM también es dinero: cada llamada extra al modelo y a una API externa cuesta. Sin caché, el servidor MCP puede empezar a golpear el servicio/modelo externo tan a menudo que la factura mensual sorprenda. Un caché bien hecho reduce latencia y coste.
Error n.º 7: Mezclar datos de distintas locales/regiones en una misma caché.
GiftGenius opera en varios países, pero en la caché se usa una sola clave top_gifts. Resultado: un usuario de EE. UU. ve rublos y tiendas rusas, y un usuario de Europa — dólares y tiendas de EE. UU. Al cachear ten siempre en cuenta claves como locale, currency, tenant en el nombre de la clave de caché o en la ruta (por ejemplo, /api/{locale}/gifts/top).
Error n.º 8: Dependencia total de la «magia» de Next.js/plataforma.
ISR, revalidate, CDN automático — todo eso es genial. Pero si no entiendes qué pasa bajo el capó, es fácil obtener efectos inesperados. Por ejemplo, una página muestra contenido viejo y el API devuelve el nuevo; ChatGPT ve una cosa y los usuarios en navegador — otra. Dedica tiempo a entender cómo funcionan Cache-Control, ETag y el patrón SWR, y usa Next.js como envoltorio útil, no como caja negra.
Error n.º 9: No diferenciar dev/staging/production en la caché.
En desarrollo, la caché a menudo estorba al debug («he cambiado los datos, ¿por qué ChatGPT sigue viendo selecciones antiguas?»). Conviene tener una configuración que en dev casi desactive la caché (o TTL de pocos segundos) y en production active una caché agresiva. De lo contrario, o enloqueces durante el desarrollo, o sin querer despliegas a producción sin caché y recibes una tormenta de solicitudes a los clústeres backend detrás del MCP Gateway.
GO TO FULL VERSION