diff --git a/components/webui/server/src/routes/static.ts b/components/webui/server/src/routes/static.ts index c07204866a..910ecde9fb 100644 --- a/components/webui/server/src/routes/static.ts +++ b/components/webui/server/src/routes/static.ts @@ -1,12 +1,45 @@ import path from "node:path"; import {fileURLToPath} from "node:url"; -import {fastifyStatic} from "@fastify/static"; +import fastifyStatic from "@fastify/static"; import {FastifyPluginAsync} from "fastify"; import settings from "../../settings.json" with {type: "json"}; +const CACHE_CONTROL_HEADER_KEY = "Cache-Control"; +const CACHE_CONTROL_HEADER_VALUE_NO_CACHE = "public, max-age=0"; + +// Cache all other static files for 1 year without revalidation. +const CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE = "public, max-age=31536000, immutable"; + + +/** + * Configures `Cache-Control` header for static files to reduce network traffic. + * + * @param res + * @param reqPath + * @param extraNoCachePaths + */ +const setCacheHeaders = ( + res: Parameters>[0], + reqPath: string, + extraNoCachePaths: string[] = [] +): void => { + const noCachePaths = [ + "/index.html", + ...extraNoCachePaths, + ]; + + if (noCachePaths.some((noCachePath) => reqPath.endsWith(noCachePath))) { + res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_NO_CACHE); + + return; + } + + res.setHeader(CACHE_CONTROL_HEADER_KEY, CACHE_CONTROL_HEADER_VALUE_LONG_TERM_CACHE); +}; + /** * Creates static files serving routes. * @@ -40,21 +73,32 @@ const routes: FastifyPluginAsync = async (fastify) => { logViewerDir = path.resolve(rootDirname, logViewerDir); } await fastify.register(fastifyStatic, { + decorateReply: false, prefix: "/log-viewer", root: logViewerDir, - decorateReply: false, + + // Prevent fastify-static from adding its own cache headers and provide our own. + cacheControl: false, + setHeaders: (res, reqPath) => { + setCacheHeaders(res, reqPath); + }, }); let clientDir = settings.ClientDir; if (false === path.isAbsolute(clientDir)) { clientDir = path.resolve(rootDirname, settings.ClientDir); } - await fastify.register(fastifyStatic, { + decorateReply: true, prefix: "/", root: clientDir, - decorateReply: true, wildcard: false, + + // Prevent fastify-static from adding its own cache headers and provide our own. + cacheControl: false, + setHeaders: (res, reqPath) => { + setCacheHeaders(res, reqPath, ["/settings.json"]); + }, }); // Serve index.html for all unmatched routes in the React Single Page Application (SPA).