====== 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]]