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...
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:
- showing a preview of one post was always fine, but a different one caused the error
- incremental builds were fine, but fresh builds failed
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;
}
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)
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:
-
one post and not the other — the posts belong to different collections
-
incremental vs fresh builds — the collection declaration says it’s “used to inform both
--incremental
builds and template build order” (source). I don’t fully understand what’s happening, but that--incremental
is mentioned makes it seem relevant.
However, I’m not sure how to apply this to handle my case. The way it works is:
- 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.
- The shortcode looks up the post from
collections.all
(in.eleventy.js
) - Then, the shortcode renders a preview of the post, including possibly getting an excerpt from its content (in
.eleventy.js
; excerpt getting in acommon.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"]
An important note here: on excluding the post that’s displaying others (i.e., the “monthly digest”) from collections with eleventyExcludeFromCollections: true
:
-
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!
-
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
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.