fix(serveur): index.html ET index.css jamais mis en cache
CI / build-and-test (push) Successful in 15m0s

/index.css n'est pas fingerprinté (index.html le référence en dur) mais
héritait de `maxAge: '1h'` : après un redéploiement F5 continuait de servir
l'ANCIEN CSS jusqu'à une heure — correctif présent sur le serveur, invisible
au navigateur (« je ne vois toujours pas la ligne fournisseur »). Il rejoint
index.html dans le `setHeaders` no-cache (ETag → 304, coût nul).

Test : `index.html et index.css : jamais mis en cache` dans
server/tests/api_coverage.test.mjs (29 pass / 0 fail).
This commit is contained in:
2026-10-03 10:19:51 -04:00
parent 64821a8538
commit 80ac48da20
2 changed files with 21 additions and 1 deletions
+6 -1
View File
@@ -3937,7 +3937,12 @@ app.use(express.static(staticDir, {
index: 'index.html',
setHeaders: (res, filePath) => {
try {
if (String(filePath || '').toLowerCase().endsWith('.html')) {
// index.html ET index.css sont les SEULS fichiers non fingerprintés
// (index.html référence /index.css en dur). Avec maxAge 1h, F5 leur
// laissait l'ANCIEN fichier jusqu'à une heure après un redéploiement —
// d'où « je ne vois pas le correctif » alors que le serveur est à jour.
const name = path.basename(String(filePath || '')).toLowerCase();
if (name === 'index.html' || name === 'index.css') {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.removeHeader('Expires');
+15
View File
@@ -235,6 +235,21 @@ describe('production-ready (P0/P1)', () => {
}
});
it('index.html et index.css : jamais mis en cache (seuls fichiers non fingerprintés)', async () => {
for (const p of ['/', '/index.css']) {
const r = await fetch(`${base}${p}`);
assert.equal(r.status, 200, p);
const cc = r.headers.get('cache-control') || '';
// Sans revalidation, F5 continue de servir l'ANCIEN fichier jusqu'à 1 h
// après un redéploiement : le correctif est bien sur le serveur mais
// invisible au navigateur.
assert.match(cc, /no-cache/, `${p} doit être revalidé à chaque chargement (reçu : ${cc || 'vide'})`);
assert.ok(!/max-age=[1-9]/.test(cc), `${p} ne doit pas porter de max-age positif (${cc})`);
}
const css = await fetch(`${base}/index.css`);
assert.match(css.headers.get('content-type') || '', /css/, '/index.css doit servir du CSS');
});
it('X-Request-Id renvoyé sur chaque réponse', async () => {
const r = await fetch(`${base}/api/health`);
assert.ok(r.headers.get('x-request-id'), 'header X-Request-Id absent');