1. Pourquoi penser au cache et à l’edge dans une app ChatGPT
Dans une application web classique, vous vous souciez déjà des performances, mais l’utilisateur y voit au moins un spinner. Dans une ChatGPT App, la situation est plus subtile. L’utilisateur discute avec le modèle, qui décide parfois d’appeler votre App. Le widget doit apparaître et afficher rapidement quelque chose d’utile.
La pratique est assez claire : latence = argent. Plus vous répondez lentement, plus l’utilisateur risque de partir, et les appels LLM/backend inutiles sont des coûts directs pour les modèles et l’infrastructure. Le cache réduit les deux.
Spécificités des ChatGPT Apps :
- Les requêtes de ChatGPT vers votre App passent par le réseau et diverses couches. Chaque milliseconde à chaque étape s’additionne.
- Les endpoints MCP/HTTP ont de vrais timeouts (y compris pour les fonctions serverless et edge de Vercel). Si vous n’êtes pas assez rapide, ChatGPT voit une erreur et peut même commencer à « halluciner » une réponse.
- Beaucoup de données dans GiftGenius ne changent pas chaque seconde : structure du catalogue de cadeaux, sélections de « meilleures idées » pour différents segments, réglages de fonctionnalités. Inutile d’interroger la base ou une API externe à chaque fois.
Et c’est là qu’entrent en jeu :
- CDN et cache edge, pour distribuer rapidement la statique et le JSON cachable.
- Cache HTTP avec Cache-Control/ETag/SWR, afin que les requêtes répétées soient plus rapides et moins coûteuses.
- Fonctions edge Vercel, pour exécuter une logique légère au plus près de ChatGPT et de l’utilisateur, sans en faire « un mini‑backend ».
2. Anatomie des latences dans GiftGenius et points de mise en cache
Il est d’abord utile de cartographier honnêtement où naît la latence.
sequenceDiagram
participant User as Utilisateur
participant ChatGPT as ChatGPT
participant App as ChatGPT App (Apps SDK)
participant GW as MCP Gateway / Edge
participant GiftAPI as Gift REST API / microservice de cadeaux
participant DB as Catalogue/Base
User->>ChatGPT: "Choisis un cadeau pour mon frère"
ChatGPT->>App: Appel d'outil + rendu du widget
App->>GW: Requête HTTP/MCP (catégories, sélections)
GW->>GiftAPI: HTTP (REST)
GiftAPI->>DB: Requête de catalogue/recommandations
DB-->>GiftAPI: Réponse
GiftAPI-->>GW: Réponse (JSON)
GW-->>App: Réponse (JSON)
App-->>ChatGPT: Widget avec résultats
ChatGPT-->>User: Message + UI
Où peut‑on « gagner du temps » ici ?
- Entre ChatGPT et votre périmètre — CDN/cache edge (Vercel CDN/Edge Network), qui peut distribuer des assets statiques immuables et du JSON cachable sans joindre votre serveur d’origine.
- Entre le Gateway et les services REST/HTTP internes (Gift REST API, Commerce REST API, etc.) et la base — cache applicatif (Redis/en mémoire/cache BD), pour éviter d’exécuter dix fois les mêmes requêtes (par ex. « liste des catégories de cadeaux »).
Dans ce cours, nous nous concentrons sur la couche HTTP/edge, car elle est la plus proche de ChatGPT et Vercel.
3. Types de cache dans notre architecture
Puisque notre architecture est « en couches », il y a plusieurs caches.
| Type de cache | Où il vit | À quoi il sert |
|---|---|---|
| Cache du navigateur | Dans le client ChatGPT (navigateur/desktop) | Statique du widget, icônes, polices (contrôle limité) |
| CDN / cache edge | Sur les nœuds edge Vercel/Cloudflare | Statique + JSON partagé (catégories, configs, sélections communes) |
| Cache applicatif | Dans votre MCP Gateway ou services backend (Redis, en mémoire) | Résultats de requêtes lourdes vers la BD/APIs externes |
| Cache BD / matérialisation | Dans la BD elle‑même (vues matérialisées, etc.) | Agrégats pré‑calculés, analytique |
Concentrons‑nous maintenant sur les deux premiers : cache HTTP + CDN/edge.
4. Cache HTTP : Cache-Control, max-age et s-maxage
Le cache HTTP est principalement contrôlé par l’en‑tête Cache-Control. Il détermine si le navigateur/le client ChatGPT et/ou le CDN peuvent mettre en cache votre réponse, et pour combien de temps.
Points clés :
- max-age — nombre de secondes pendant lesquelles le navigateur peut mettre en cache la réponse.
- s-maxage — nombre de secondes pendant lesquelles un cache partagé (CDN/proxy) peut mettre en cache.
- public — la réponse peut être mise en cache dans un cache partagé.
- private — la réponse est réservée au client spécifique ; le CDN ne la met pas en cache.
Dans GiftGenius, par exemple :
- JS/CSS/polices du widget — fichiers versionnés (avec hash dans le nom), on peut les servir avec Cache-Control : max-age=31536000, immutable.
- JSON avec la liste des catégories de cadeaux — identique pour tous les utilisateurs, ici il est logique d’utiliser public, s-maxage=60 (ou plus).
Un Route Handler Next.js simple pour GET /api/gifts/categories, mis en cache sur le CDN pendant 60 secondes :
// app/api/gifts/categories/route.ts
import { NextResponse } from "next/server";
export const runtime = "nodejs"; // fonction serverless standard
export async function GET() {
// ici on pourrait interroger la BD/une API externe
const categories = [
{ id: "for_brother", title: "Cadeaux pour un frère" },
{ id: "for_mom", title: "Cadeaux pour maman" },
];
return NextResponse.json(categories, {
headers: {
// on autorise le CDN à mettre en cache 60 secondes
"Cache-Control": "public, s-maxage=60",
},
});
}
Le CDN de Vercel conservera la réponse pendant 60 secondes, et toutes les requêtes de ChatGPT pour ce JSON durant cette fenêtre n’atteindront même pas votre fonction. C’est instantané et peu coûteux.
5. ETag : empreinte du contenu et 304 Not Modified
ETag est une sorte « d’empreinte » d’une ressource, généralement un hash du contenu. Fonctionnement :
- Le serveur renvoie une réponse avec l’en‑tête ETag: "v1-abc123".
- La fois suivante, le client envoie l’en‑tête If-None-Match: "v1-abc123".
- Si le serveur estime que le contenu n’a pas changé, il répond 304 Not Modified sans corps.
Important : ETag économise de la bande passante, mais ne réduit pas nécessairement la latence, car un aller‑retour réseau reste nécessaire. Dans le contexte des ChatGPT Apps, c’est utile pour des réponses JSON lourdes, mais n’attendez pas des miracles de vitesse d’un simple ETag — pour cela, préférez SWR et le cache edge.
Exemple d’un ETag simple dans un handler Next.js (sans hash cryptographique, pour rester concis) :
// 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) {
// Le contenu n'a pas changé — on renvoie 304
return new NextResponse(null, { status: 304, headers: { ETag: ETAG } });
}
return NextResponse.json(CONFIG, {
headers: {
ETag: ETAG,
"Cache-Control": "public, s-maxage=300",
},
});
}
En pratique, vous calculerez l’ETag à partir d’un hash des données ou utiliserez la version de l’enregistrement en BD.
6. Stale‑While‑Revalidate (SWR) : rapide et suffisamment frais
SWR est l’approche « afficher l’ancien immédiatement, puis actualiser en arrière‑plan ». Elle peut être mise en œuvre :
- Au niveau de l’en‑tête HTTP Cache-Control avec le paramètre stale-while-revalidate.
- Au niveau de l’UI, avec des bibliothèques comme swr/react-query qui conservent un cache local et relancent des rafraîchissements en arrière‑plan.
SWR dans l’en‑tête HTTP
En‑tête typique :
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
Idée :
- Pendant les 60 premières secondes, le CDN sert la version fraîche.
- De la 61e à la 360e seconde, le CDN peut renvoyer une réponse périmée immédiatement, et déclencher en arrière‑plan une requête vers l’origine pour obtenir la nouvelle version.
- Après 360 secondes, la requête pour du contenu neuf redevient bloquante.
L’utilisateur (et ChatGPT) obtient une réponse instantanément même en pic de charge, tandis que vous mettez à jour le cache en douceur en arrière‑plan. Pour GiftGenius, c’est idéal par exemple pour les « meilleures sélections de cadeaux pour le Nouvel An » — elles ne changent pas toutes les secondes.
Exemple :
// app/api/gifts/top/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const topGifts = [
{ id: "coffee_mug", title: "Mug avec inscription" },
{ id: "smart_led", title: "Lampe intelligente" },
];
return NextResponse.json(topGifts, {
headers: {
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
},
});
}
SWR dans le widget UI (React)
Le widget GiftGenius vit dans le bac à sable de ChatGPT et peut utiliser n’importe quel code React. Vous savez déjà appeler votre API via window.fetch. Ajoutons la bibliothèque swr et mettons en place un cache côté 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 } // le focus change bizarrement dans le chat, on désactive
);
if (isLoading && !data) return <div>Chargement des idées...</div>;
return (
<ul>
{data?.map((gift: any) => (
<li key={gift.id}>{gift.title}</li>
))}
</ul>
);
}
Comment ça marche :
- Au premier rendu, une requête est envoyée vers notre API.
- Le résultat est stocké dans le cache de swr à l’intérieur du widget.
- Lors des rendus suivants (ou de nouvelles réponses où ChatGPT réinsère ce widget avec la même clé), les données sont lues depuis le cache. L’utilisateur ne voit pas de « clignotements » ni de spinners, et une mise à jour peut partir en arrière‑plan.
Ainsi, nous combinons deux niveaux de SWR :
- Au niveau CDN/HTTP — pour ne pas charger l’origine.
- Au niveau UI — pour ne pas charger l’utilisateur.
En résumé :
- Un simple Cache-Control (max-age/s-maxage) — couche de base : on autorise le CDN et les clients à mettre en cache les réponses et à réduire la charge.
- ETag + If-None-Match — à ajouter quand il faut économiser la bande passante pour des JSON lourds, en acceptant l’aller‑retour réseau.
- stale-while-revalidate — à activer quand on veut une distribution instantanée même de données légèrement périmées (catalogues, sélections top).
- SWR dans l’UI (bibliothèque swr/react-query) — couche dédiée pour lisser les rerenders du widget et le cache local dans le bac à sable de ChatGPT.
7. Que mettre en cache dans GiftGenius et combien de temps
Essayons de classer les données de GiftGenius par « couches de cachabilité ».
Peut être mis en cache au niveau du CDN/edge
Tout ce qui est identique pour tout le monde (ou de larges segments) et change rarement :
- Statique du widget : JS/CSS, polices, icônes — quasi « pour toujours » (un an) avec immutable.
- Structure des catalogues de cadeaux : catégories, sections, filtres — minutes/heures.
- Sélections générales (« meilleures idées pour des collègues jusqu’à 50 $ ») — minutes/dizaines de minutes, surtout en périodes de pointe.
Ici, public, s-maxage + stale-while-revalidate sont parfaits.
À mieux cacher côté application/Redis
Des données plus dynamiques mais tout de même répétitives :
- Résultats d’APIs externes lourdes (par ex., taux de change, prix actuels d’une boutique externe).
- Segments de recommandations fréquemment demandés (par genre/âge/occasion).
Ici, le CDN ne convient pas toujours, car les données peuvent dépendre d’un jeton/d’une organisation/d’un tenant. On met en cache au niveau du MCP Gateway ou des services REST internes : c’est entièrement sous votre contrôle et cela évite de mélanger les données des utilisateurs.
À ne pas mettre en cache (dans des caches partagés)
Ce qui est lié à un utilisateur spécifique :
- Commandes personnelles et statuts de commande.
- Informations de paiement, adresses, e‑mail.
- Recommandations spécifiques basées sur l’historique d’achats privé (si sensible).
Cela peut être mis en cache uniquement côté application avec une sémantique soigneuse (et surtout sans fuite entre utilisateurs), mais certainement pas dans le cache CDN public.
8. Couche edge : CDN vs fonctions edge
Il ne faut pas confondre deux bêtes proches mais différentes :
- CDN / cache edge — stocke des réponses pré‑calculées, il n’y a quasiment pas de logique.
- Fonctions edge (Vercel Edge / Cloudflare Workers) — de petits morceaux de code exécutés sur les nœuds edge.
L’expérience montre : Edge ≠ Serverless. Beaucoup de développeurs essaient d’y mettre une logique métier lourde, des appels LLM et du traitement de BLOB, puis s’étonnent des timeouts et des limites. Les fonctions edge :
- Démarrent très vite (cold start quasi nul).
- Mais sont fortement limitées en CPU, temps d’exécution et APIs disponibles (souvent sans Node.js complet, sans sockets longue durée, etc.).
Quand une fonction edge est une bonne idée
Dans le contexte de GiftGenius et des ChatGPT Apps, les fonctions edge sont utiles pour :
- Un routage léger : décider, selon les en‑têtes locale, x-openai-user-location ou l’ID de tenant, vers quel cluster backend régional envoyer la requête.
- Ajout d’en‑têtes simples, feature flags, routage A/B.
- Endpoints read‑only rapides qui lisent depuis un edge‑KV ou le cache CDN et ne calculent presque rien.
Quand une fonction edge est une mauvaise idée
- Requêtes longues vers des APIs externes.
- Appels à des modèles LLM.
- Logique complexe de checkout.
- Outils MCP avec logique métier lourde.
Pour tout cela, vous avez les fonctions serverless classiques de Next.js (par exemple, runtime = "nodejs") ou carrément des services/clusters séparés.
Exemple de fonction edge dans Next.js 16
Créons une petite route GET /api/geo-router, qui renverra, selon l’en‑tête x-openai-user-location (supposé), le cluster régional à contacter.
// app/api/geo-router/route.ts
import { NextRequest, NextResponse } from "next/server";
export const runtime = "edge"; // exécuté sur l'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 tel endpoint :
- Fonctionne très vite (edge).
- Ne fait rien de complexe.
- Peut être mis en cache par le CDN.
9. Edge et cache dans l’architecture globale de GiftGenius
Mettons tout ensemble.
flowchart TD
ChatGPT[(ChatGPT / User)]
CDN["CDN / Edge Cache (Vercel)"]
EdgeFn["Edge Functions (routage, feature flags)"]
GW[MCP Gateway]
GiftAPI["Gift REST API Cluster"]
CommerceAPI["Commerce REST API Cluster"]
DB[(BD/APIs externes)]
ChatGPT --> CDN
CDN -->|hit de cache| ChatGPT
CDN -->|miss de cache| EdgeFn
EdgeFn --> GW
GW --> GiftAPI
GW --> CommerceAPI
GiftAPI --> DB
CommerceAPI --> DB
Scénario typique :
- Le widget ChatGPT demande /api/gifts/categories.
- Le CDN vérifie son cache. S’il a une version fraîche ou « stale mais encore valable », il la renvoie immédiatement, sans toucher EdgeFn/GW.
- Si le cache est vide — la requête tombe dans EdgeFn (s’il est activé) et/ou directement dans GW.
- GW utilise si besoin un cache Redis interne pour les opérations lourdes ou appelle les services REST internes puis la BD.
- La réponse revient, entre dans le cache CDN/edge et est servie aux autres utilisateurs.
Une telle architecture :
- Réduit la latence pour le widget et ChatGPT.
- Diminue la charge sur le MCP Gateway et les clusters backend.
- Réduit le coût des appels LLM/BD (moins de requêtes répétées).
10. Quelques fragments pratiques pour GiftGenius
Cache des catégories + revalidate de Next.js
Jusqu’ici, nous avons parlé des endpoints d’API. Mais Next.js fournit des mécanismes similaires pour les pages via ISR (revalidate).
Exemple de composant serveur qui récupère la liste des catégories avec revalidate = 60 :
// app/(widget)/categories/page.tsx
export const revalidate = 60; // ISR: regénérer toutes les 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 production, Vercel va générer et mettre en cache la sortie HTML de cette page, ce qui est utile si votre widget/interface est ouvert non seulement via ChatGPT, mais aussi comme une page web classique (par ex., un panneau de debug ou un landing).
Application de cache simple dans un service backend
Ce n’est plus la couche edge, mais le cache applicatif (Redis/en mémoire à l’intérieur de votre Gift REST API ou d’un autre service backend). Mais c’est l’occasion de montrer à quoi il ressemble dans sa forme la plus simple :
// pseudo-code à l'intérieur de 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 60 secondes
}
const data = await fetchRealCategories();
cache.set(key, { ts: Date.now(), data });
return data;
}
En production, vous remplacerez bien sûr la Map par Redis/Memcached, mais l’idée est la même : réduire les appels à la BD/aux APIs externes.
Si l’on résume en une phrase : commencez par décider clairement quoi peut être mis en cache et où (CDN, edge, Redis, BD), puis activez les « flags magiques » de la plateforme. Le cache n’est pas une simple case à cocher dans la config, c’est une partie de l’architecture : il influe sur la vitesse, la stabilité et les coûts.
11. Erreurs typiques avec le cache et la couche edge
Erreur n°1 : « Mettre tout en cache pour aller plus vite ».
Grand classique : un développeur met Cache-Control: public, s-maxage=3600 sur toutes les réponses JSON. Quelques heures plus tard, on découvre qu’un utilisateur voit les commandes d’un autre, et ChatGPT commence à manipuler des données obsolètes sur la disponibilité produit. Pour des données personnelles ou sensibles, il faut un cache private ou désactiver le cache CDN et garder un cache côté application avec une isolation stricte.
Erreur n°2 : Confusion entre max-age et s-maxage.
Certains ne définissent que max-age et s’attendent à ce que le CDN mette en cache exactement autant. En réalité, max-age s’applique d’abord au navigateur, et pour les caches partagés il faut s-maxage. Résultat : le navigateur met en cache, mais pas le CDN, et l’origine continue d’étouffer sous la charge, alors que « le cache est activé ». La bonne approche : indiquer explicitement s-maxage pour le CDN.
Erreur n°3 : Penser que ETag accélère tout.
ETag économise très bien la bande passante, notamment pour les gros JSON, mais l’aller‑retour réseau reste. Dans le monde des ChatGPT Apps, cela signifie que le modèle attend quand même une réponse de votre serveur, même si c’est un 304 sans corps. Si vous visez principalement la latence, il faut du cache edge + SWR, et ETag est un mécanisme d’appoint.
Erreur n°4 : Tenter de mettre une logique métier lourde dans les fonctions edge.
« Appelons une LLM externe, calculons des sélections complexes et interrogeons trois APIs externes directement depuis Vercel Edge — c’est rapide, non ? » Puis viennent la douleur : limites de temps d’exécution, absence d’un vrai Node.js, erreurs étranges. L’edge convient au routage léger et à l’A/B, tandis que tout le lourd doit aller dans des fonctions serverless classiques ou des clusters backend séparés.
Erreur n°5 : Absence de stratégie d’invalidation du cache.
Vous avez mis un cache « d’une heure », tout vole. Puis le métier dit : « nous avons changé les prix/les catégories/les contraintes, pourquoi ChatGPT montre‑t‑il encore l’ancien contenu ? » Les développeurs commencent à bricoler, purger les caches et redémarrer les services. Pour des données importantes, il faut prévoir à l’avance : comment allez‑vous invalider le cache (via webhook depuis l’admin, par version, par clé), au lieu de compter sur « cela se mettra à jour tout seul dans une heure ».
Erreur n°6 : Ignorer le lien cache ↔ coût.
Parfois, on ne pense au cache qu’en termes de vitesse. Dans l’écosystème LLM, c’est aussi une question d’argent : chaque appel supplémentaire au modèle et à une API externe coûte. Sans cache, le serveur MCP peut marteler un service externe/modèle si souvent que la facture mensuelle surprendra désagréablement. Un cache correct réduit à la fois la latence et la facture.
Erreur n°7 : Mélanger des données de locales/régions différentes dans un seul cache.
GiftGenius fonctionne dans plusieurs pays, mais la clé de cache top_gifts est unique. Résultat : un utilisateur aux États‑Unis voit des roubles et des boutiques russes, et un utilisateur en Europe — des dollars et des boutiques américaines. Lors de la mise en cache, prenez toujours en compte des clés comme locale, currency, tenant dans le nom de la clé de cache ou dans l’URL (par ex., /api/{locale}/gifts/top).
Erreur n°8 : Dépendre entièrement de la « magie » de Next.js/de la plateforme.
ISR, revalidate, CDN automatique — tout cela est super. Mais si vous ne comprenez pas ce qui se passe sous le capot, vous obtiendrez facilement des effets inattendus. Par exemple, une page affiche un ancien contenu alors que l’API renvoie le nouveau ; ChatGPT voit une chose, les utilisateurs dans le navigateur — une autre. Prenez le temps de comprendre Cache-Control, ETag et le pattern SWR, et utilisez Next.js comme une enveloppe pratique, pas comme une boîte noire.
Erreur n°9 : Pas de différenciation dev/staging/production pour le cache.
En environnement de dev, le cache gêne souvent le debug (« j’ai modifié les données, pourquoi ChatGPT voit‑il encore les anciennes sélections ? »). Il est utile d’avoir une configuration qui quasi désactive le cache en dev (ou met un TTL de quelques secondes), et l’active agressivement en production. Sinon, soit vous devenez fou pendant le développement, soit vous déployez par inadvertance en prod sans cache et vous subissez une tempête de requêtes vers les clusters backend derrière le MCP Gateway.
GO TO FULL VERSION