Eleventy Collections Fix Tag Navigation with Proper Filtering

Summary

The issue involves a global collection definition in an Eleventy (11ty) project that fails to respect context-specific filtering. The developer created a custom collection named postsAscending that performs a global sort on all files matching specific globs. However, because this collection is defined by hardcoded file paths rather than dynamic filtering, it ignores the tags associated with a specific category page. When a user clicks a tag, the system returns the entire sorted list of posts instead of a filtered subset, rendering the tag navigation functionality useless.

Root Cause

The failure stems from a misunderstanding of how Eleventy Collections interact with the build lifecycle:

  • Hardcoded Glob Patterns: The collection uses .getFilteredByGlob("gallery/*.md", "blog/*.md"), which explicitly tells the engine to pull every file in those directories regardless of any other criteria.
  • Lack of Context Awareness: The custom collection is a static list generated at build time. It does not “know” which tag a template is currently rendering.
  • Over-reliance on Custom Collections: The developer attempted to solve a filtering problem by creating a sorting collection, effectively bypassing the built-in tagging logic that Eleventy provides.

Why This Happens in Real Systems

In complex production systems, this pattern is known as The Global State Trap. It occurs when:

  • Abstraction Leaks: A developer creates a “helper” function or collection meant to simplify data access, but that helper is too rigid to handle varied contexts.
  • Bypassing the Data Pipeline: Instead of following the natural flow of data (Data -> Filter -> Template), the developer creates a “shortcut” (Custom Collection) that skips the filtering stage.
  • Premature Optimization: Trying to define a “perfectly sorted” collection globally instead of allowing the template engine to handle sorting on a per-page basis.

Real-World Impact

  • Broken User Experience (UX): Users navigating by category see irrelevant content, leading to high bounce rates and loss of trust in the site’s navigation.
  • Performance Degradation: As the number of posts grows, forcing the system to process and sort a massive global list for every single tag page increases build times and memory usage.
  • Maintenance Debt: Any change to the file structure or naming conventions requires updates to hardcoded strings within the configuration file, making the system brittle.

Example or Code

To fix this, the developer should use the built-in collections.tags or filter the existing collections.all within the template itself, rather than creating a specialized global collection.

// The WRONG way (Current implementation)
eleventyConfig.addCollection("postsAscending", (collection) => {
  return collection.getFilteredByGlob("gallery/*.md", "blog/*.md")
    .sort((a, b) => a.data.title.localeCompare(b.data.title));
});

// The RIGHT way (Logic should reside in the template or a generic helper)
// In the Liquid template for a tag page:
// {% for post in collections.tags[currentTag] | sort | reverse %}
//   {{ post.data.title }}
// {% endfor %}

How Senior Engineers Fix It

A senior engineer approaches this by prioritizing composability and separation of concerns:

  1. Decouple Sorting from Filtering: Instead of one function that does both, create a generic sorting utility or use the template engine’s built-in filters to sort data after it has been filtered by the tag.
  2. Leverage Built-in Data Structures: Utilize the standard collections.all or collections.tags provided by the framework, which are already context-aware.
  3. Dynamic Templating: Use the template context (e.g., collections.tags[tag]) to drive the content, ensuring the logic scales automatically as new tags are added without requiring config changes.
  4. Implement Locale-Aware Sorting: Use localeCompare instead of manual if/else blocks to ensure special characters and different languages are sorted correctly.

Why Juniors Miss It

  • Focusing on the “How” instead of the “Where”: A junior focuses on how to sort the list (the math/logic), while a senior focuses on where that logic should live in the data lifecycle.
  • Confusing Global vs. Local Scope: Juniors often assume that a collection defined in the config file is a “smart” object that adapts to the page, whereas it is actually just a static array.
  • The “Working Code” Fallacy: The code “works” (it shows posts and it sorts them), so the junior assumes the logic is correct, failing to test the edge case of tag-based filtering.

Leave a Comment