Status: not started, idea only. Nothing in this repo has been changed to
prepare for this. Written up so it can be picked up and evaluated properly in
a separate session, after weighing it against the plainereleventy-3-upgrade-plan.md fix for the same
underlying pain (full rebuilds being slow).
Every /habits/* page is fully rendered at build time: Nunjucks pulls_data/habits/<slug>.json through _11ty/habits-data.js (loadHabits,buildTimeline, stat aggregation) and bakes stat tiles, an inline SVG trend
chart (_11ty/habits.js's habitTrendChart), and monthly/daily tables
straight into HTML. Appending one day's Duolingo sessions to_data/habits/duolingo.json and rebuilding forces the entire site through
Eleventy's full page-write path - confirmed at 562 pages / ~153s locally for
this repo's current state (2026-08-03), because every one of those pages
(not just the habit pages) runs the same expensive per-page transform chain:
JSDOM parse ×4 (apply-csp.js, img-dim.js, json-ld.js, the vendoredlocalimages transform) plus PurgeCSS and html-minifier (both in_11ty/optimize-html.js). None of that is specific to habit data changing -
it's just what a full eleventy run does.
Two independent levers exist for this:
eleventy-3-upgrade-plan.md) - 3.x has aThey're not mutually exclusive - the upgrade makes every page's rebuild
faster; this idea makes habit pages stop needing an HTML rebuild for a data
change at all. Worth deciding whether to do one, the other, or both, but do
the upgrade first and re-assess whether this is still worth the complexity
once it's done.
_data/csp.js sets default-src 'self'script-src 'self' <hashes>. default-src 'self' covers fetch() toconnect-src needed. script-src 'self'<script src="..."> withcsp-hash attribute, generated in _11ty/apply-csp.js)<script> tags. So a runtime-render script.html. purifyCss,minifyHtml, and optimizeAmp in _11ty/optimize-html.js all checkoutputPath.endsWith(".html") before running; apply-csp.js's CSP-hash.html file (e.g. a.json file) already skips all four - no new gating logic needed.feed/json.njk has front matter settingpermalink: "/feed/feed.json" and a JSON body, producing_site/feed/feed.json. The same technique (a .njk template with a JSONpermalink) is the natural way to emit a per-habit_data/habits/*.json isn't currently copied to _site at all - it's_11ty/habits-data.js's loadHabits() (fs.readFileSync +JSON.parse on every call - not memoized, called once each from thegetHabit filter, _data/habitsIndex.js, and _data/duolingoMonths.js)addPassthroughCopy. So there's no existing URL a client fetch could hitloadHabits()'s output, with stats/sessionCount/computeExtraStats/pickBest/average/formatMetricValuehabitTrendChart in _11ty/habits.js is already framework-free - pureMath, templateescapeXml). It's a plausible, fairly mechanical portKeep _11ty/habits-data.js as the single source of truth for aggregation -
don't duplicate loadHabits/buildTimeline/stat math in client JS. Instead:
feed/json.njk pattern:src/pages/habits/index.data.11ty.js (or .njk) → /habits/data.json,habitsIndex (list + timeline)..11ty.js template witheleventyComputed.permalink paginating over loadHabits() (mirroringduolingo/duolingo.11tydata.js already computes a permalink per/habits/<slug>/data.json for every habit_data/habits/*.json files are added..html-gated transforms above) - so regenerating them is fast even<div id="habit-app" data-habit-url="/habits/duolingo/data.json"></div>.rollup.config.js / src/main.js already produces js/min.js; thisdata.json, and renders the stat tiles, ahabitTrendChart (SVG string → innerHTML, or built/habits/ index could similarly fetch /habits/data.json and render<div> that only works with JS. Decide how far thishabit-app.js for all habit pages (simpler,/habits/duolingo/ only (highest entry-frequency/habits/sudoku/ or the /habits/ index./habits/duolingo/data.json output template; verify its outputgetHabit("duolingo") currently produces forhabitTrendChart to a small, dependency-free browser module; unittest/ conventions already/habits/duolingo/, decide the/habits/sudoku/ and the/habits/ index, or stop at the one-habit prototype if the trade-offsSmall-to-medium. The JSON-emission templates are a few lines each (precedent
already exists in feed/json.njk); most of the effort is thehabitTrendChart port and getting the progressive-enhancement fallback
right without regressing CLS/perceived load. Meaningfully smaller than the
Eleventy 3.x upgrade, but do that first and revisit whether this is still
wanted.