Favicon-caching en 404 op ontbrekende statische bestanden

This commit is contained in:
Jordy Hoogeveen
2026-09-09 11:04:26 +02:00
parent 1d1ab487d9
commit 2a4edee192
6 changed files with 95 additions and 9 deletions
+20 -7
View File
@@ -5,7 +5,7 @@ import rateLimit from "express-rate-limit";
import cookieParser from "cookie-parser";
import pinoHttp from "pino-http";
import { existsSync } from "fs";
import { join } from "path";
import { extname, join, relative, sep } from "path";
import { config } from "./config/index.js";
import { logger } from "./logger.js";
import { initDb } from "./db.js";
@@ -73,21 +73,34 @@ const indexHtml = join(config.STATIC_DIR, "index.html");
export const servesFrontend = existsSync(indexHtml);
if (servesFrontend) {
// Assets hebben een hash in de naam en mogen lang gecached worden;
// index.html juist nooit, anders blijft een oude build hangen.
// Alleen de bundels in /assets krijgen bij elke build een nieuwe, gehashte
// naam — die mogen een jaar blijven staan. Bestanden met een vaste naam
// (favicon.svg, icons/*) moeten revalideren: met "immutable" blijft een
// eenmaal verkeerd gecacht antwoord een jaar hangen, ook na een reload.
app.use(express.static(config.STATIC_DIR, {
index: false,
setHeaders(res, path) {
res.setHeader("Cache-Control", path.endsWith(".html")
? "no-store, no-cache, must-revalidate"
: "public, max-age=31536000, immutable");
setHeaders(res, bestandspad) {
const relatief = relative(config.STATIC_DIR, bestandspad).split(sep).join("/");
if (bestandspad.endsWith(".html")) {
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
} else if (relatief.startsWith("assets/")) {
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
} else {
res.setHeader("Cache-Control", "public, max-age=0, must-revalidate");
}
},
}));
// SPA-fallback: alle overige GET-routes naar index.html. Pathloze middleware
// in plaats van app.get("*") — dat patroon is in Express 5 ongeldig.
//
// Paden met een extensie vragen om een bestand (/favicon.ico, /robots.txt)
// en krijgen een echte 404. Anders levert zo'n verzoek index.html op met
// status 200 en content-type text/html: de browser krijgt HTML waar hij een
// icoon verwacht en toont dan niets, zonder zichtbare fout.
app.use((req, res, next) => {
if (req.method !== "GET" && req.method !== "HEAD") return next();
if (extname(req.path)) return next();
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
res.sendFile(indexHtml);
});