index.html wurde ein Jahr lang zwischengespeichert
Die maxAge-Option von fastify-static setzt den Cache-Header fuer jede Datei, auch fuer die index.html. Der setHeaders-Callback, der das fuer HTML wieder aufheben sollte, kam dagegen nicht an: ausgeliefert wurde durchgehend public, max-age=31536000. Damit bekam ein Browser nach einem Update weiter die alte Oberflaeche - ein Jahr lang, ohne Fehlermeldung, obwohl der Server laengst neue Dateien hatte. Genau so ist der Abgleich-Knopf nach dem Deploy unsichtbar geblieben. Jetzt setzt die App beide Faelle selbst: HTML immer neu holen, Assets unbegrenzt behalten. Deren Dateinamen enthalten einen Inhalts-Hash, sie aendern sich bei neuem Inhalt ohnehin. Drei Tests pruefen die Header, weil ein Fehler an dieser Stelle nirgends auffaellt - er sieht aus, als waere das Update nicht angekommen.
This commit is contained in:
+11
-4
@@ -128,11 +128,18 @@ export async function buildApp(): Promise<FastifyInstance> {
|
||||
await app.register(fastifyStatic, {
|
||||
root: webRoot,
|
||||
prefix: '/',
|
||||
// Gehashte Assets duerfen lange liegen bleiben, index.html nie.
|
||||
maxAge: '1y',
|
||||
// maxAge wuerde den Header fuer *alle* Dateien setzen und dabei auch die
|
||||
// index.html erwischen - die muss aber bei jedem Aufruf neu geholt werden,
|
||||
// sonst zeigt der Browser nach einem Update monatelang die alte Oberflaeche.
|
||||
// Deshalb hier abschalten und beides unten selbst setzen.
|
||||
cacheControl: false,
|
||||
setHeaders: (res, path) => {
|
||||
if (path.endsWith('index.html')) {
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
if (path.endsWith('.html')) {
|
||||
res.setHeader('Cache-Control', 'no-cache, must-revalidate');
|
||||
} else {
|
||||
// Assets tragen einen Inhalts-Hash im Namen: aendert sich der Inhalt,
|
||||
// aendert sich der Dateiname. Die duerfen beliebig lange liegen.
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -286,3 +286,36 @@ describe('Guthaben ueber die API', () => {
|
||||
assert.equal(response.json().error, 'NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Auslieferung der Oberflaeche', () => {
|
||||
// Diese Header sind der Grund, warum ein Update beim Nutzer ankommt. Wird die
|
||||
// index.html cachebar ausgeliefert, sieht er nach einem Deploy weiter die
|
||||
// alte Oberflaeche - ohne dass irgendwo ein Fehler auftaucht.
|
||||
it('laesst die index.html nicht zwischenspeichern', async () => {
|
||||
const response = await app.inject({ method: 'GET', url: '/' });
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.match(
|
||||
response.headers['cache-control'] as string,
|
||||
/no-cache/,
|
||||
'die index.html muss bei jedem Aufruf neu geholt werden',
|
||||
);
|
||||
});
|
||||
|
||||
it('liefert auch Deep-Links ohne Cache aus', async () => {
|
||||
const response = await app.inject({ method: 'GET', url: '/players/1' });
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.match(response.headers['cache-control'] as string, /no-cache/);
|
||||
});
|
||||
|
||||
it('laesst gehashte Assets lange liegen', async () => {
|
||||
const page = await app.inject({ method: 'GET', url: '/' });
|
||||
const asset = /\/assets\/[^"]+\.js/.exec(page.body)?.[0];
|
||||
assert.ok(asset, 'die Seite verweist auf ein Asset');
|
||||
|
||||
const response = await app.inject({ method: 'GET', url: asset });
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.match(response.headers['cache-control'] as string, /max-age=31536000/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user