From 229f770cbbde82c8a033aa91961dc7c06c96fc3d Mon Sep 17 00:00:00 2001 From: Ricardo Date: Sun, 8 Mar 2026 12:52:42 +0100 Subject: [PATCH 01/54] perf: force V8 garbage collection after Eleventy builds Add global.gc() call in eleventy.after handler to release unused heap pages back to the OS. Without this, V8 keeps ~2 GB of build-time allocations resident in watch mode because there's no allocation pressure to trigger GC naturally. Requires --expose-gc in NODE_OPTIONS (set in start.sh for the watcher process). Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88 --- eleventy.config.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/eleventy.config.js b/eleventy.config.js index 5a61754..f1c5924 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -1333,6 +1333,19 @@ export default function (eleventyConfig) { } } + // Force garbage collection after post-build work completes. + // V8 doesn't return freed heap pages to the OS without GC pressure. + // In watch mode the watcher sits idle after its initial full build, + // so without this, ~2 GB of build-time allocations stay resident. + // Requires --expose-gc in NODE_OPTIONS (set in start.sh for the watcher). + if (typeof global.gc === "function") { + const before = process.memoryUsage(); + global.gc(); + const after = process.memoryUsage(); + const freed = ((before.heapUsed - after.heapUsed) / 1024 / 1024).toFixed(0); + console.log(`[gc] Post-build GC freed ${freed} MB (heap: ${(after.heapUsed / 1024 / 1024).toFixed(0)} MB)`); + } + // WebSub hub notification — skip on incremental rebuilds if (incremental) return; const hubUrl = "https://websubhub.com/hub"; From 9e8f0f139aaa4ce97c2592af26aaca29b021634d Mon Sep 17 00:00:00 2001 From: Ricardo Date: Sun, 8 Mar 2026 15:23:27 +0100 Subject: [PATCH 02/54] perf: remove skeleton loader to fix CLS (0.916 mobile / 1.004 desktop) The skeleton-to-content swap was the root cause of extreme CLS scores. Critical CSS already provides correct first-paint layout, making the skeleton unnecessary. Removes html.loading class, skeleton div, page-content wrapper, and all skeleton CSS rules. Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88 --- _includes/layouts/base.njk | 26 +++----------------------- css/critical.css | 9 +-------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk index 053c598..49bab81 100644 --- a/_includes/layouts/base.njk +++ b/_includes/layouts/base.njk @@ -1,5 +1,5 @@ - + {# OG image resolution handled by og-fix transform in eleventy.config.js to bypass Eleventy 3.x parallel rendering race condition (#3183). @@ -57,7 +57,7 @@ {# Critical CSS — inlined for fast first paint #} {# Defer full stylesheet — loads after first paint #} - + @@ -90,9 +90,7 @@ [x-data] > .flex.border-b { display: none !important; } /* Hide loading spinners and JS-only buttons */ [x-show*="loading"], button[\\@click*="fetch"], button[\\@click*="loadMore"] { display: none !important; } - /* Show content and hide skeleton for no-JS (stylesheet loads synchronously via noscript link) */ - .page-skeleton { display: none !important; } - html.loading main.container > .page-content { display: block !important; } + /* Content is always visible — no skeleton loader */ @@ -282,23 +280,6 @@
- {# Skeleton loader — shown until Tailwind stylesheet loads #} - - -
{% if withSidebar and page.url == "/" and homepageConfig and homepageConfig.sections %} {# Homepage: builder controls its own layout and sidebar #} {{ content | safe }} @@ -323,7 +304,6 @@ {% else %} {{ content | safe }} {% endif %} -