diff --git a/_includes/base.njk b/_includes/base.njk
index a1186f7..d94be0d 100644
--- a/_includes/base.njk
+++ b/_includes/base.njk
@@ -4,8 +4,43 @@
{{ title }} | Volpe
-
-
+
+ {# Critical CSS inlined for faster initial render #}
+
+
+ {# Preload full stylesheet with cache-busted filename #}
+
+
+
+ {# Defer prism.css - only needed for syntax highlighting #}
+
+
+
diff --git a/eleventy.config.js b/eleventy.config.js
index f1d7345..6bf9b09 100644
--- a/eleventy.config.js
+++ b/eleventy.config.js
@@ -4,8 +4,26 @@ const markdownItFootnote = require("markdown-it-footnote");
const markdownItMermaid = require('markdown-it-mermaid').default
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const fs = require("fs");
+const crypto = require("crypto");
+const path = require("path");
const { DateTime } = require("luxon");
+const cssHashCache = {};
+const getCssHash = (cssFile) => {
+ if (cssHashCache[cssFile]) return cssHashCache[cssFile];
+
+ const cssPath = path.join(__dirname, "css", cssFile);
+ try {
+ const content = fs.readFileSync(cssPath, "utf-8");
+ const hash = crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
+ cssHashCache[cssFile] = hash;
+ return hash;
+ } catch (e) {
+ console.warn(`Could not hash CSS file: ${cssFile}`);
+ return null;
+ }
+};
+
const extractTags = (content, mdInstance) => {
if (!content) return [];
@@ -220,7 +238,36 @@ module.exports = (eleventyConfig) => {
return tagMap;
});
- eleventyConfig.addPassthroughCopy("css");
+ // Cache busting filter: returns hashed CSS filename
+ eleventyConfig.addFilter("cssHash", (cssFile) => {
+ const hash = getCssHash(cssFile);
+ const ext = path.extname(cssFile);
+ const base = path.basename(cssFile, ext);
+ return `/css/${base}.${hash}${ext}`;
+ });
+
+ eleventyConfig.on("eleventy.before", async () => {
+ const cssDir = path.join(__dirname, "css");
+ const outputCssDir = path.join(__dirname, "_site", "css");
+
+ if (!fs.existsSync(outputCssDir)) {
+ fs.mkdirSync(outputCssDir, { recursive: true });
+ }
+
+ const cssFiles = fs.readdirSync(cssDir).filter(f => f.endsWith(".css"));
+ for (const cssFile of cssFiles) {
+ const hash = getCssHash(cssFile);
+ const ext = path.extname(cssFile);
+ const base = path.basename(cssFile, ext);
+ const hashedName = `${base}${hash == null ? '' : `.${hash}`}${ext}`;
+
+ fs.copyFileSync(
+ path.join(cssDir, cssFile),
+ path.join(outputCssDir, hashedName)
+ );
+ }
+ });
+
eleventyConfig.addPassthroughCopy("robots.txt");
eleventyConfig.ignores.add("README.md");
diff --git a/nix/module.nix b/nix/module.nix
index 2ddc24b..796dc2d 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -20,6 +20,14 @@
locations."/" = {
tryFiles = "$uri $uri/ /index.html";
};
+ # Cache static assets (CSS, JS, images) for 1 year with immutable
+ locations."~* \\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$" = {
+ extraConfig = ''
+ expires 1y;
+ add_header Cache-Control "public, max-age=31536000, immutable";
+ access_log off;
+ '';
+ };
};
in {
options.services.volpe = {