ETag / GET conditionnel
L'endpoint émet un en-tête ETag. Les requêtes suivantes avec If-None-Match: <etag> reçoivent un 304 sans corps — même vérification de fraîcheur, quasi-zéro octet. Associez avec cache-control: max-age=N, must-revalidate pour garder le cache navigateur chaud tout en revalidant après expiration.
Astuce
Un ETag fort (entre guillemets : <code>"abc123"</code>) garantit une correspondance exacte octet par octet. Un ETag faible (préfixé <code>W/</code>) autorise une équivalence sémantique. Préférez les ETags forts pour les API JSON : le corps doit être identique bit à bit, pas seulement « équivalent ». Dérivez l'ETag d'un hash du corps (<code>fnv1a</code>, <code>sha256</code>) plutôt que d'un horodatage pour éviter les faux miss.
Essayez
Premier clic : 200 avec corps. Clics suivants : 304 (l'ETag du serveur correspond à celui envoyé).
// src/app/api/etag-data/route.ts — conditional GET.
const ETAG = '"' + fnv1a(BODY) + '"';
export async function GET(req: Request): Promise<Response> {
if (req.headers.get("if-none-match") === ETAG) {
return new Response(null, { status: 304, headers: { ETag: ETAG } });
}
return new Response(BODY, { status: 200, headers: {
"content-type": "application/json",
ETag: ETAG,
"cache-control": "public, max-age=10, must-revalidate",
}});
}
// Client revalidates on focus — fetch + ETag means most checks are
// 304s with no body, so the cost is one round-trip with no payload.
window.addEventListener("focus", () => fetch("/api/etag-data"));