1. Perché pensare a cache ed edge in una ChatGPT App
In una classica applicazione web ti preoccupi comunque della velocità, ma lì l’utente vede almeno uno spinner. Nella ChatGPT App la situazione è più interessante. L’utente conversa con il modello, che a volte decide di chiamare la tua App. Il widget deve apparire e mostrare qualcosa di utile abbastanza in fretta.
La pratica è abbastanza univoca: latency = denaro. Più a lungo impieghi a rispondere, maggiore è la probabilità che l’utente se ne vada, e le chiamate extra alla LLM/al backend sono costi diretti per modelli e infrastruttura. Il caching riduce entrambe le cose.
In più, c’è la specificità delle ChatGPT Apps:
- Le richieste da ChatGPT alla tua App passano attraverso la rete e vari strati. Ogni millisecondo in ogni passaggio si somma.
- Gli endpoint MCP/HTTP hanno time-out reali (anche le serverless e le edge functions di Vercel). Se non fai in tempo, ChatGPT vede un errore e può persino iniziare a «inventare» una risposta.
- Molti dati in GiftGenius non cambiano ogni secondo: struttura del catalogo dei regali, raccolte di «idee top» per diversi segmenti, impostazioni delle feature. È sciocco colpire ogni volta di nuovo il database o un’API esterna.
Ed è proprio qui che entrano in gioco:
- CDN ed edge cache, per servire velocemente statici e JSON cachabili.
- HTTP cache con Cache-Control/ETag/SWR, per rendere le richieste ripetute più rapide ed economiche.
- Edge functions di Vercel, per eseguire logica leggera il più vicino possibile a ChatGPT e all’utente, senza trasformarle in un «mini-backend».
2. Anatomia della latenza in GiftGenius e punti di caching
Per prima cosa è utile disegnare onestamente dove nasce la latency.
sequenceDiagram
participant User as Utente
participant ChatGPT as ChatGPT
participant App as ChatGPT App (Apps SDK)
participant GW as MCP Gateway / Edge
participant GiftAPI as Gift REST API / microservizio dei regali
participant DB as Catalogo/Database
User->>ChatGPT: "Scegli un regalo per mio fratello"
ChatGPT->>App: Chiamata dello strumento + render del widget
App->>GW: Richiesta HTTP/MCP (categorie, raccolte)
GW->>GiftAPI: HTTP (REST)
GiftAPI->>DB: Richiesta al catalogo/raccomandazioni
DB-->>GiftAPI: Risposta
GiftAPI-->>GW: Risposta (JSON)
GW-->>App: Risposta (JSON)
App-->>ChatGPT: Widget con i risultati
ChatGPT-->>User: Messaggio + UI
Dove possiamo «tagliare gli angoli»?
- Tra ChatGPT e il tuo perimetro — CDN/edge cache (Vercel CDN/Edge Network), che può servire asset del widget immutabili e JSON cachabile senza raggiungere il tuo origin server.
- Tra il Gateway e i servizi REST/HTTP interni (Gift REST API, Commerce REST API, ecc.) e il database — cache applicativa (Redis/in memoria/cache DB), per non ripetere le stesse richieste (per esempio, «elenco delle categorie di regali») dieci volte.
In questa lezione ci concentriamo proprio sul layer HTTP/edge, perché è più vicino a ChatGPT e Vercel.
3. Tipi di cache nella nostra architettura
Dato che la nostra architettura è «a strati», anche le cache sono diverse.
| Tipo di cache | Dove risiede | Per cosa è adatta |
|---|---|---|
| Cache del browser | All'interno del client ChatGPT (browser/desktop) | Risorse statiche del widget, icone, font (controllo limitato) |
| CDN / edge cache | Sui nodi edge Vercel/Cloudflare | Statici + JSON condiviso (categorie, config, raccolte generali) |
| Cache applicativa | All'interno del tuo MCP Gateway o dei servizi backend (Redis, in‑memory) | Risultati di query pesanti al DB/API esterni |
| Cache DB/materializzazione | Nel DB stesso (materialized views, ecc.) | Aggregati precomputati, analitica |
Ora concentriamoci sui primi due: HTTP cache + CDN/edge.
4. HTTP cache: Cache-Control, max-age e s-maxage
L’HTTP cache è governata principalmente dall’header Cache-Control. Da questo dipende se il browser/client ChatGPT e/o il CDN possono mettere in cache la tua risposta e per quanto tempo.
I punti chiave:
- max-age — per quanti secondi il browser può mettere in cache la risposta.
- s-maxage — per quanti secondi può mettere in cache il shared cache (CDN/proxy).
- public — la risposta può essere messa in cache nello shared cache.
- private — la risposta è solo per il client specifico; il CDN non la mette in cache.
In GiftGenius, ad esempio:
- JS/CSS/font del widget — file versionati (con hash nel nome), possono essere serviti tranquillamente con Cache-Control: max-age=31536000, immutable.
- Il JSON con l’elenco delle categorie dei regali — uguale per tutti gli utenti, qui ha senso public, s-maxage=60 (o più).
Un semplice Route Handler Next.js per GET /api/gifts/categories, messo in cache sul CDN per 60 secondi:
// app/api/gifts/categories/route.ts
import { NextResponse } from "next/server";
export const runtime = "nodejs"; // normale funzione serverless
export async function GET() {
// qui potremmo interrogare il DB/un'API esterna
const categories = [
{ id: "for_brother", title: "Regali per il fratello" },
{ id: "for_mom", title: "Regali per la mamma" },
];
return NextResponse.json(categories, {
headers: {
// consentiamo al CDN di mettere in cache per 60 secondi
"Cache-Control": "public, s-maxage=60",
},
});
}
Il CDN di Vercel conserverà la risposta per 60 secondi, e tutte le richieste di ChatGPT a questo JSON durante quella finestra non raggiungeranno affatto la tua funzione. È istantaneo ed economico.
5. ETag: impronta del contenuto e 304 Not Modified
ETag è una sorta di «impronta digitale» della risorsa, di solito un hash del contenuto. Schema di funzionamento:
- Il server restituisce una risposta con l’header ETag: "v1-abc123".
- La volta successiva il client invia l’header If-None-Match: "v1-abc123".
- Se il server ritiene che il contenuto non sia cambiato, risponde con 304 Not Modified senza body.
Importante: ETag risparmia traffico, ma non riduce necessariamente la latenza, perché serve comunque un round trip al server. Nel contesto delle ChatGPT Apps è utile per risposte JSON pesanti, ma non aspettarti miracoli in termini di velocità dal solo ETag — per quello è meglio usare SWR e edge cache.
Esempio di ETag semplice in un Route Handler Next.js (senza hash crittografici, per semplicità):
// 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) {
// Il contenuto non è cambiato — restituiamo 304
return new NextResponse(null, { status: 304, headers: { ETag: ETAG } });
}
return NextResponse.json(CONFIG, {
headers: {
ETag: ETAG,
"Cache-Control": "public, s-maxage=300",
},
});
}
Nella vita reale, naturalmente, calcolerai l’ETag dall’hash dei dati o userai la versione del record nel DB.
6. Stale‑While‑Revalidate (SWR): veloce e sufficientemente aggiornato
SWR è l’approccio «mostra subito il vecchio, aggiorna in background». Può essere realizzato:
- A livello di header HTTP Cache-Control con il parametro stale-while-revalidate.
- A livello UI, usando librerie come swr/react-query, che mantengono una cache locale e fanno refetch in background.
SWR nell’header HTTP
Header tipico:
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
Significato:
- Nei primi 60 secondi il CDN restituisce la versione fresca.
- Dal 61° al 360° secondo il CDN può restituire una risposta obsoleta istantaneamente, e in background avviare la richiesta all’origine per la nuova versione.
- Dopo 360 secondi la richiesta per il nuovo contenuto diventa bloccante.
L’utente (e ChatGPT) ottiene la risposta istantaneamente anche nei picchi di carico, mentre tu aggiorni dolcemente la cache in background. Per GiftGenius è ideale, ad esempio, per le «raccolte top di regali per Capodanno» — non cambiano ogni secondo.
Esempio:
// app/api/gifts/top/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const topGifts = [
{ id: "coffee_mug", title: "Tazza con scritta" },
{ id: "smart_led", title: "Lampada smart" },
];
return NextResponse.json(topGifts, {
headers: {
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
},
});
}
SWR nel widget UI (React)
Il widget GiftGenius vive nella sandbox di ChatGPT e può usare qualsiasi codice React. Sai già come chiamare la tua API tramite window.fetch. Aggiungiamo la libreria swr e impostiamo una cache lato 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 } // nel chat il focus cambia in modo strano, disattiviamolo
);
if (isLoading && !data) return <div>Caricamento delle idee...</div>;
return (
<ul>
{data?.map((gift: any) => (
<li key={gift.id}>{gift.title}</li>
))}
</ul>
);
}
Come funziona:
- Al primo render parte una richiesta alla nostra API.
- Il risultato viene messo nella cache di swr all’interno del widget.
- Ai render successivi (o in nuove risposte in cui ChatGPT inserisce di nuovo questo widget con la stessa chiave) i dati vengono presi dalla cache. L’utente non vede «sfarfallii» e spinner, e in background può partire un aggiornamento.
Così combiniamo due livelli di SWR:
- Su CDN/HTTP — per non caricare l’origine.
- Nell’UI — per non caricare l’utente.
Se mettiamo tutto insieme:
- Semplice Cache-Control (max-age/s-maxage) — livello di base: diamo a CDN e client il permesso di mettere in cache le risposte e ridurre il carico.
- ETag + If-None-Match — aggiungi quando è importante risparmiare traffico per JSON pesanti, accettando però il round trip di rete.
- stale-while-revalidate — attiva quando conta la consegna istantanea anche di dati leggermente obsoleti (cataloghi, raccolte top).
- SWR nell’UI (libreria swr/react-query) — livello separato per smussare i re-render del widget e mantenere una cache locale nella sandbox di ChatGPT.
7. Cosa mettere in cache in GiftGenius e per quanto
Proviamo a distribuire i dati di GiftGenius per «livelli di cacheabilità».
Da mettere in cache a livello CDN/edge
Tutto ciò che è uguale per tutti (o per segmenti ampi) e cambia raramente:
- Statici del widget: JS/CSS, font, icone — praticamente «per sempre» (un anno) con immutable.
- Struttura dei cataloghi dei regali: categorie, sezioni, filtri — minuti/ore.
- Raccolte generali («migliori idee per i colleghi fino a 50 $») — minuti/decine di minuti, specialmente nei picchi stagionali.
Qui è ideale public, s-maxage + stale-while-revalidate.
Meglio mettere in cache nell’applicazione/Redis
Dati più dinamici, ma comunque ripetitivi:
- Risultati di API esterne pesanti (ad esempio, vai a prendere i tassi di cambio, i prezzi aggiornati di un negozio esterno).
- Segmenti di raccomandazioni richiesti spesso (per sesso/età/occasione).
Qui il CDN non sempre va bene, perché i dati possono dipendere da token/organizzazione/tenant. Metti in cache a livello di MCP Gateway o servizi REST interni: è completamente sotto il tuo controllo e non mescola i dati tra utenti diversi.
Da non mettere in cache (nei cache condivisi)
Ciò che è legato a uno specifico utente:
- Ordini personali e stati degli ordini.
- Informazioni di pagamento, indirizzi, email.
- Raccomandazioni specifiche basate sulla cronologia ordini privata (se sensibile).
Questo si può mettere in cache solo a livello applicativo con semantica accurata (e senza alcuna fuga di dati tra utenti), ma sicuramente non nel cache public del CDN.
8. Layer edge: CDN contro edge functions
È importante non confondere due bestie simili ma diverse:
- CDN / edge cache — conserva risposte pre-calcolate, lì c’è poca logica.
- Edge functions (Vercel Edge / Cloudflare Workers) — piccoli pezzi di codice eseguiti sui nodi edge.
L’esperienza mostra: Edge ≠ Serverless. Molti sviluppatori cercano di infilarci dentro logica di business pesante, chiamate a LLM e elaborazione di BLOB, e poi si stupiscono per time-out e limiti. Le edge functions:
- Si avviano molto velocemente (cold start quasi zero).
- Ma sono fortemente limitate in CPU, tempo di esecuzione e API disponibili (spesso senza Node.js completo, senza socket lunghi, ecc.).
Quando un’edge function è una buona idea
Nel contesto di GiftGenius e della ChatGPT App le edge functions sono utili per:
- Instradamento leggero: in base agli header locale, x-openai-user-location o tenant ID decidere a quale cluster backend regionale inviare la richiesta.
- Aggiungere header semplici, feature flag, instradamento A/B.
- Endpoint read-only veloci, che leggono dati da edge-KV o dal cache del CDN e praticamente non fanno calcoli.
Quando un’edge function è una cattiva idea
- Richieste lunghe verso API esterne.
- Chiamate a modelli LLM.
- Logica complessa del checkout.
- Strumenti MCP con logica di business pesante.
Per tutto questo hai le normali serverless function di Next.js (ad esempio, runtime = "nodejs") o persino servizi/cluster separati.
Esempio di edge function in Next.js 16
Creiamo una piccola rotta GET /api/geo-router, che in base all’header x-openai-user-location (ipotetico) restituisce a quale cluster regionale rivolgersi.
// app/api/geo-router/route.ts
import { NextRequest, NextResponse } from "next/server";
export const runtime = "edge"; // eseguito all'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",
},
});
}
Questo endpoint:
- Funziona molto velocemente (edge).
- Non fa nulla di complesso.
- Può essere messo in cache dal CDN.
9. Edge e cache nell’architettura complessiva di GiftGenius
Mettiamo tutto in un’unica figura.
flowchart TD
ChatGPT[(ChatGPT / User)]
CDN["CDN / Edge Cache (Vercel)"]
EdgeFn["Edge Functions (instradamento, feature flag)"]
GW[MCP Gateway]
GiftAPI["Gift REST API Cluster"]
CommerceAPI["Commerce REST API Cluster"]
DB[(DB/External APIs)]
ChatGPT --> CDN
CDN -->|cache hit| ChatGPT
CDN -->|cache miss| EdgeFn
EdgeFn --> GW
GW --> GiftAPI
GW --> CommerceAPI
GiftAPI --> DB
CommerceAPI --> DB
Scenario tipico:
- Il widget di ChatGPT richiede /api/gifts/categories.
- Il CDN controlla la cache. Se c’è una versione fresca o «stale ma ancora valida» — la restituisce subito, senza toccare EdgeFn/GW.
- Se la cache non c’è — la richiesta passa a EdgeFn (se attivata) e/o direttamente a GW.
- GW, se necessario, usa una cache interna Redis per le operazioni pesanti o chiama i servizi REST interni e poi il DB.
- La risposta torna indietro, finisce nel CDN/edge cache e viene servita agli altri utenti.
Questa impostazione:
- Riduce la latenza per il widget e ChatGPT.
- Riduce il carico su MCP Gateway e cluster backend.
- Riduce i costi delle chiamate a LLM/DB (meno richieste ripetute).
10. Piccoli frammenti pratici per GiftGenius
Cache delle categorie + revalidate di Next.js
Finora abbiamo parlato solo degli endpoint API. Ma Next.js offre meccanismi simili anche per le pagine — tramite ISR (revalidate).
Esempio di server component che ottiene l’elenco delle categorie con revalidate = 60:
// app/(widget)/categories/page.tsx
export const revalidate = 60; // ISR: rigenerazione ogni 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>
);
}
In produzione Vercel genererà e metterà in cache l’output HTML di questa pagina, utile nei casi in cui il tuo widget/interfaccia sia aperto non solo tramite ChatGPT, ma anche come normale pagina web (ad esempio, una debug panel o una landing).
Semplice cache applicativa in un servizio backend
Questo non è più il layer edge, ma la cache applicativa (Redis/in‑memory all’interno della tua Gift REST API o di un altro servizio backend). Ma è utile mostrare come si presenti nella forma più semplice:
// pseudo-code all'interno di 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; // cache di 60 secondi
}
const data = await fetchRealCategories();
cache.set(key, { ts: Date.now(), data });
return data;
}
In produzione, ovviamente, sostituirai la Map con Redis/Memcached, ma l’idea è la stessa: andare meno spesso sul DB/sull’API esterna.
Se dovessimo comprimere tutto in un unico assunto: prima decidi chiaramente cosa si può mettere in cache e dove (CDN, edge, Redis, DB), e solo dopo attiva le «flag magiche» della piattaforma. La cache non è una spunta in config, è parte dell’architettura: influisce su velocità, stabilità e costi.
11. Errori tipici nell’uso di cache ed edge layer
Errore n. 1: «Mettiamo in cache tutto a caso, purché sia più veloce».
Un classico: lo sviluppatore imposta Cache-Control: public, s-maxage=3600 su tutte le risposte JSON. Dopo qualche ora si scopre che un utente vede gli ordini di un altro, e ChatGPT inizia a usare dati vecchi sulla disponibilità dei prodotti. Per dati personali o sensibili serve una cache private, oppure disabilitare del tutto la cache del CDN e tenere la cache a livello applicativo con isolamento accurato.
Errore n. 2: Confusione tra max-age e s-maxage.
Alcuni impostano solo max-age e si aspettano che il CDN metta in cache per lo stesso tempo. In realtà max-age riguarda soprattutto il browser, mentre per lo shared cache serve s-maxage. Risultato: il browser mette in cache, il CDN no, e l’origine continua a soffrire sotto carico, anche se «la cache è attiva». La strada giusta è indicare esplicitamente s-maxage per il CDN.
Errore n. 3: Aspettarsi che ETag acceleri tutto.
ETag fa risparmiare traffico, specialmente per JSON grandi, ma il round trip di rete rimane. Nel mondo delle ChatGPT App significa: il modello attende comunque la risposta dal tuo server, anche se è un 304 senza body. Se ti interessa la latenza, servono edge cache + SWR; ETag è un meccanismo ausiliario.
Errore n. 4: Cercare di infilare logica di business pesante nelle edge functions.
«Chiamiamo una LLM esterna, calcoliamo raccolte complesse e contattiamo tre API esterne direttamente da Vercel Edge — è veloce!» Poi inizia il dolore: limiti sul tempo di esecuzione, assenza di un vero Node.js, errori strani. L’edge è ottimo per instradamento leggero e A/B, mentre tutto ciò che è pesante deve andare nelle normali serverless function o in cluster/backend separati.
Errore n. 5: Mancanza di una strategia di invalidazione della cache.
Hai impostato una cache «di un’ora», tutto vola. Poi il business dice: «abbiamo cambiato prezzi/categorie/restrizioni, perché in ChatGPT è tutto come prima?» Gli sviluppatori iniziano a tirare leve manualmente, svuotare cache e riavviare servizi. Per dati importanti, pensa in anticipo: come azzererai la cache (tramite webhook dal pannello admin, per versione, per chiave), invece di contare su «si aggiornerà da sola tra un’ora».
Errore n. 6: Ignorare il legame cache ↔ costo.
A volte gli sviluppatori pensano alla cache solo come velocità. Nell’ecosistema LLM è anche una questione di soldi: ogni chiamata extra al modello e a un’API esterna costa. Senza cache, l’MCP server può iniziare a colpire il servizio esterno/il modello così spesso che il conto mensile sorprenderà. Un caching corretto riduce sia la latenza, sia il conto.
Errore n. 7: Mescolare dati di località/regioni diverse nella stessa cache.
GiftGenius opera in diversi paesi, ma nella cache si usa un’unica chiave top_gifts. Risultato: l’utente dagli USA vede i prezzi in rubli e negozi russi, e l’utente europeo — dollari e negozi USA. Quando fai caching, considera sempre chiavi come locale, currency, tenant nel nome della chiave o nella rotta (ad esempio, /api/{locale}/gifts/top).
Errore n. 8: Dipendere completamente dalla «magia» di Next.js/della piattaforma.
ISR, revalidate, CDN automatico — tutto fantastico. Ma se non capisci cosa succede sotto il cofano, è facile avere effetti imprevisti. Per esempio, una pagina mostra contenuto vecchio e l’API restituisce quello nuovo; ChatGPT vede una cosa e gli utenti nel browser un’altra. Conviene spendere tempo per capire come funzionano Cache-Control, ETag e il pattern SWR, e usare Next.js come comoda astrazione, non come scatola nera.
Errore n. 9: Nessuna differenza tra dev/staging/production per la cache.
In ambiente di sviluppo la cache spesso intralcia il debug («ho cambiato i dati, perché ChatGPT vede ancora le vecchie raccolte?»). È utile avere una config che in dev quasi disattivi la cache (o metta TTL di pochi secondi), mentre in production attivi un caching aggressivo. Altrimenti impazzirai in sviluppo o, al contrario, rilascerai in produzione senza cache e subirete una tempesta di richieste sui cluster backend dietro l’MCP Gateway.
GO TO FULL VERSION