Better excerpts in Eleventy
| Tags: front-end
Excerpts are short extracts of an article —usually the first sentences—, and are used in blogs to show only a fragment of a post, either to break a long article and put a “Read more…” link, or to display a list of posts with a bit more of context than just the title. In this article I’ll share how I implemented excerpts for this website.
Eleventy, the system that I’m now using to generate this blog, has an API to handle excerpts, but it has some shortcomings:
- It requires you to manually generate the excerpt by using some kind of separator in the post’s content, like
---
or<!-- excerpt -->
. - In the templates, when you want to access a post’s excerpt, you have the raw contents. So if you were using Markdown, it will not get translated to HTML.
While useful, it didn’t quite handle my use case. I wanted to have an implicit, default excerpt for my articles and I also wanted to use Markdown for my excerpts. After some searching, I found a plugin that handled default excerpts —but bypassed Eleventy’s own API, so I couldn’t have both implicit and explicit excerpts. And for Markdown parsing, I could just use a npm module to do that.
In the end, I used the plugin’s idea to just graph the first paragraph of a post as a default excerpt, unless there was one already defined via Eleventy’s API, and then use the markdown-it
module to parse it. This is how it looks like:
First, we separate the excerpts code in a separate file, for readability.
plugins/excerpt.js
:
const markdown = require('markdown-it');
module.exports = function excerpt(item) {
const separator = '</p>';
const excerpt = item.data?.page?.excerpt;
// If it has an explicit excerpt (see setFrontMatterParsingOptions),
// use it.
if (excerpt) {
return markdown({ html: true }).render(excerpt);
}
// If there's no explicit excerpt, use the first paragraph as the
// excerpt. This is already parsed to HTML, so no need to use
// markdown-it here
const location = item.templateContent?.indexOf(separator);
return location >= 0
? item.templateContent.slice(0, location + separator.length)
: '';
};
Then Eleventy’s config file would look like this:
.eleventy.js
:
// import my own plugin
const excerpt = require('./plugins/excerpt');
module.exports = function (eleventyConfig) {
// ...
// Setup excerpts
eleventyConfig.setFrontMatterParsingOptions({
excerpt: true,
excerpt_separator: '<!-- excerpt -->',
});
eleventyConfig.addShortcode('excerpt', (article) => {
return excerpt(article);
});
};
With that in place, in the templates I only need to use the shortcode excerpt
to output it:
{%- for article in collections.articles -%}
<article>
<h3>{{ article.data.title }}</h3>
{% excerpt article %}
</article>
{%- endfor -%}
And that’s about it. Quick and simple, and since it uses Eleventy’s own API, I hope it won’t give much headache to maintain in the future.