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.tsTypeScript
// 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"));