Word count and reading time in Eleventy

Want to give your readers an idea of what’s ahead? Here’s some code to make that no biggie.


There are numerous articles out there which give you what I consider fairly convoluted procedures for adding word count and reading time statistics to your posts in the Eleventy static site generator (SSG).

So, how’d you like something simpler? Something you can do strictly within a Nunjucks template? (Except for one teensy little Eleventy filter, that is.)

Here you go.

First, here’s a template file derived from my billboard.njk:

{% set regExpCode = r/<pre class=(.|\n)*?<\/pre>/gm %}
{% set fixedContent = content | replace(regExpCode, "") | striptags %}
{% set wordCount = fixedContent | wordcount %}
{% set readingRate = 225 %}
{% set readingTime = (wordCount/readingRate) | round %}
{% if readingTime < 1 %}{% set readingTime = 1 %}{% endif %}
	{{ wordCount | numCommas }} words • Reading time: {{ readingTime }} minute{% if readingTime > 1 %}s{% endif %}

Let’s break down what’s happening here, and keep in mind that everything below (but for, again, one tiny exception) involves stuff built into Nunjucks.

  • The set regExpCode line creates a regular expression for the HTML that Eleventy wraps around code blocks (at least, that’s true if you’re handling syntax highlighting through the most typical methods; but, if you’re doing it differently, adjust the regex accordingly).
  • The set fixedContent line makes a fixedContent variable, which soon will be used in deriving the word count, and uses replace with the regExpCode variable to delete all code-block-related HTML from fixedContent. Then, it uses the Nunjucks striptags filter to carve the remaining HTML down to just text. (That neatly takes care of any inline images, among other things.)
  • The set wordCount line uses the wordcount filter to, well, you can guess.
  • The set readingRate line assigns 225 as the number of words per minute we’ll use in calculating the reading time. If you have a preferred number, substitute it here.
  • The set readingTime line divides wordCount by readingRate and then rounds it to the nearest integer. (But, since really short posts might end up with readingTime as 0 based on that rounding, the if readingTime < 1 line fixes that.)
  • Finally, in the paragraph, we:
    • Filter wordCount through numCommas (that’s the aforementioned exception, about which more shortly).
    • Provide readingTime, followed by either minute or minutes (depending on whether the value of readingTime exceeds 1).

The only other thing this requires is the formatting of the wordCount so that it has the proper commas for English rendering thereof — accomplished above by use of the numCommas filter, which comes from this snippet within the Eleventy config file:

  eleventyConfig.addFilter("numCommas", function(value) {
		return value.toLocaleString()

By the way, this also can be done in the Hugo SSG, using various features that come with Hugo out of the box:

{{- /*
	h/t to Joe Mooring's answer in
*/ -}}
{{- $wordCount := replaceRE `(?s)<div class="highlight">.*?</div>` "" .Content | countwords -}}
{{- $readingTime := div (float $wordCount) 225 | math.Ceil -}}
{{- $wordCountFmt := lang.FormatNumberCustom 0 $wordCount -}}
	{{ $wordCountFmt }} words &bull; Reading time: {{ $readingTime }} minute{{- if (gt $readingTime 1) -}}s{{- end -}}