Eleventy Circular References

Goal

I’m trying to have one page display previews of others. For example a “monthly digest” post shows previews of posts written in that month. Here’s an example such post preview:

Talk to Me Human Devlog

Automating Screenshots

I wrote a small script to automate taking screenshots of webpages. The script summoned enough little edge cases that I t...

ttmh devlog products

To display this, it needs to retrieve a handful of core info about a post, like its title, URL, image, date, and tags. It also might use an excerpt from the post’s content.

Problem

When trying to display a preview of a post from within another one, I frequently hit the confounding UsingCircularTemplateContentReferenceError. It is confounding because I am never trying to have a post render itself. So there ought to be no circular dependence.

The problem is extra difficult because it appeared nondeterministic. Further investigation yielded a couple patterns:

Workaround

If I paranoidly avoid ever accessing any posts’ content (i.e., post.content), this seems to fix the problem. However, it’s still frustrating because there ought to be no circular reference.

Here’s my current workaround. The offending reference (which was, as usual, impossible to tell from the stack trace) was in the getExcerpt() function, which looks at posts’ content to create an excerpt:

/**
* Usage the `safe` flag to avoid looking at `post.content` to try to avoid the
* dreaded UsingCircularTemplateContentReferenceError.
*
* @param {*} post
* @param {boolean} safe
* @returns {string} maybe custom excerpt, maybe excerpt, maybe just ""
*/

function getExcerpt(post, safe = false) {
const isSoftware = post.data.tags && post.data.tags.includes("software");
if (isSoftware) {
return post.data.summary;
}
// otherwise, it's the truncated customexcerpt / excerpt
const content = safe ? "" : post.content;
const fullExcerpt = stripTags(post.data.customexcerpt ?? content);
const excerpt = fullExcerpt.slice(0, 120) + (fullExcerpt.length > 120 ? "..." : "");
return excerpt;
}

My workaround: avoid ever looking at "post.content". I pass the "safe" flag when calling this function from the preview renderer.

Example stack trace

Here’s what I see if I don’t use the safe flag in the workaround above.

[11ty] Problem writing Eleventy templates: (more in DEBUG output)
[11ty] ./posts/blog/2024-09-10-september-2024-monthly-digest.md contains a circular reference (using collections) to its own templateContent. (via UsingCircularTemplateContentReferenceError)
[11ty]
[11ty] Original error stack trace: UsingCircularTemplateContentReferenceError: ./posts/blog/2024-09-10-september-2024-monthly-digest.md contains a circular reference (using collections) to its own templateContent.
[11ty] at TemplateMap.populateContentDataInMap (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/TemplateMap.js:608:17)
[11ty] at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
[11ty] at async TemplateMap.cache (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/TemplateMap.js:483:5)
[11ty] at async TemplateWriter._createTemplateMap (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/TemplateWriter.js:330:5)
[11ty] at async TemplateWriter.generateTemplates (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/TemplateWriter.js:360:5)
[11ty] at async TemplateWriter.write (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/TemplateWriter.js:407:23)
[11ty] at async Eleventy.executeBuild (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/Eleventy.js:1191:13)
[11ty] at async Eleventy._watch (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/Eleventy.js:822:24)
[11ty] at async watchRun (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/Eleventy.js:1047:9)
[11ty] at async FSWatcher. (/Users/max/repos/website-3/node_modules/@11ty/eleventy/src/Eleventy.js:1065:7)
[11ty] Wrote 0 files in 0.47 seconds (v2.0.1)

Stack trace I've now seen countless times, which, as usual, points to Eleventy's internal code and doesn't actually tell you what you wrote that is causing the error.

References

I flailed around a lot trying to fix this one. A handful of people online have encountered this UsingCircularTemplateContentReferenceError, but their situations often didn’t seem to match mine.

On the second day of investigation I looked back on one GitHub Issue I’d read a few times. One comment turned out to be most relevant, pointing me to specifically an excerpt function looking at post.content, rather than simply iterating over collections.all, which is what I thought the issues was from due to the stack trace (“using collections”).

Going deeper

I’d love to figure out the root cause here.

This comment on the issues points to a possible root cause: the collections referenced. This would also potentially explain the patterns under which the error occurs:

However, I’m not sure how to apply this to handle my case. The way it works is:

  1. We call a nunjucks shortcode with a URL (in monthly-digest.md)
    • We promise this URL isn’t the post itself. If it is, I fully accept a circular error.
  2. The shortcode looks up the post from collections.all (in .eleventy.js)
  3. Then, the shortcode renders a preview of the post, including possibly getting an excerpt from its content (in .eleventy.js; excerpt getting in a common.js util)

In order to look up the post from anywhere it might be, I use collections.all, as I think that’s the only place where all posts are listed. Hypothetically it’d be nice to have a collection that includes all posts but the current one, I guess?

If I try to declare a dependency (docs) to the “all” collection, I (understandably) get a circular reference error.

eleventyImport:
collections: ["all"]

Example post YAML frontmatter.

An important note here: on excluding the post that’s displaying others (i.e., the “monthly digest”) from collections with eleventyExcludeFromCollections: true:

  1. I’d rather not do that. Why should any post that renders others be forbidden from being in a collection? This post does have legitimate collections: it’s a blog post and a monthly digest!

  2. I tried it, and I still got the same error.

One thing I would like to know is: is this a true error, or is eleventy trying to lookahead detect something and getting it wrong? Or maybe the situation is somewhere in the middle: Eleventy is trying to build some kind of dependency graph and failing.

It’d probably behoove me to look at the DEBUG output.

DEBUG=Eleventy* npx eleventy --serve

Eleventy debug incantation.

WOW that’s a lot of output. Unfortunately, I don’t actually see anything more output about this particular error.

Giving up

Leaving this unresolved for now. I’ve wasted too many hours trying to figure it out already. Going to deal with not being able to look-up and excerpt posts from others which are also in collections.