====== Eleventy Gotchas ====== Common bugs and non-obvious behaviours in [[https://www.11ty.dev/|Eleventy]] (11ty) static site generator. ===== 1. HTML Escaping in Nunjucks ===== **Symptom:** Raw HTML tags visible on the rendered page — angle brackets everywhere instead of formatted content. **Cause:** Nunjucks escapes HTML by default for security. Correct when handling user input; wrong when rendering your own compiled content. **Fix:** Add the ''| safe'' filter in the base layout:
{{ content | safe }}
Static site generators are secure by default. You have to explicitly declare when you trust your own content. ===== 2. Timezone Off-By-One (Dates) ===== **Symptom:** A post dated ''2025-01-02'' renders as January 1st. Archive grouping puts December posts in November. **Cause:** JavaScript's ''Date'' object interprets bare YAML dates (''2025-01-02'') as local time, then converts based on system timezone. In a negative UTC offset, midnight January 2nd becomes the afternoon of January 1st. This is a [[https://www.11ty.dev/docs/dates/#dates-off-by-one-day|known issue documented in the 11ty docs]]. **Fix 1:** Parse dates as UTC explicitly: const date = new Date(dateObj + 'T00:00:00Z'); **Fix 2:** Use string comparison for sorting instead of date arithmetic: .sort((a, b) => String(b.data.date).localeCompare(String(a.data.date))) **Fix 3:** When grouping by month, use UTC methods — ''getFullYear()''/ ''getMonth()'' still convert to local time even after parsing as UTC: const utcYear = date.getUTCFullYear(); const utcMonth = date.getUTCMonth(); const monthKey = `${utcYear}-${String(utcMonth + 1).padStart(2, '0')}`; **Rule:** When working with dates in JavaScript, use UTC everywhere or prepare for timezone pain. ===== 3. collections.all Includes Template Files ===== **Symptom:** ''collections.all'' returns more items than you have posts — index.njk or other template files appear as collection items. **Fix:** Be explicit with glob patterns instead of relying on ''collections.all'': collectionApi.getFilteredByGlob("src/posts/**/*.md") ===== 4. Data Context Differs in Collection vs. Layout ===== **Symptom:** ''post.data.gratitude'' works in a loop on the homepage, but ''post.data.gratitude'' is undefined in the individual post layout. **Cause:** Eleventy's data cascade gives layouts a different context. When rendering a collection item in a loop, the item is ''post'' and frontmatter is ''post.data.fieldname''. When the template //is// the post's own layout, frontmatter is available directly as ''fieldname''. {# In homepage loop #} {{ post.data.gratitude }} {# In post's own layout #} {{ gratitude }} ===== 5. Archive Month Exclusion Logic ===== **Symptom:** Excluding the "current month" from archives causes posts from that month to vanish even when they're not the latest post. **Fix:** Exclude the specific latest post, not the entire month: {% for post in month.posts %} {% if not (collections.latest and post.data.date == collections.latest[0].data.date) %} {# render post #} {% endif %} {% endfor %} ===== Monthly Archive Collection ===== Full pattern for grouping posts by month with correct UTC date handling: eleventyConfig.addCollection("byMonth", function(collectionApi) { const posts = collectionApi.getFilteredByGlob("src/posts/**/*.md"); const byMonth = {}; posts.forEach(post => { const date = typeof post.data.date === 'string' ? new Date(post.data.date + 'T00:00:00Z') : post.data.date; const monthKey = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`; if (!byMonth[monthKey]) { byMonth[monthKey] = { year: date.getUTCFullYear(), month: date.toLocaleString('en-US', { month: 'long', timeZone: 'UTC' }), monthKey, posts: [] }; } byMonth[monthKey].posts.push(post); }); return Object.values(byMonth).sort((a, b) => b.monthKey.localeCompare(a.monthKey)); }); ===== See Also ===== * [[guides:start|Guides Index]] * [[indieweb:status_lol_twtxt|Syncing status.lol to twtxt]] (Eleventy global data fetch) * [[indieweb:blogging_workflow|Blogging Workflow]] (frontmatter patterns) * [[start|Return to wiki home]]