Emma Campbell
created 2022-12-17
words ~150
reading ~1 min
⚗ making

Adding Reading Time to an Article

category ⚗ making

If you've ever viewed an article on Medium, you'll notice that the preview gives an estimated "read time". This feature is easily to implement using the reading-time node package and our existing contentlayer configuration. Begin by installing reading-time.

yarn add reading-time

We will add reading time by adding a new computed field to our post model and defining the type for ReadingTime.

Define the ReadingTime Type §

We previously kept our type definitions in /content/defintions. Create a new file reading-time.ts and then use contentlayer's defineNestedType to create the new type.

import { defineNestedType } from "contentlayer/source-files";

export const ReadingTime = defineNestedType(() => {
 name: "ReadingTime",
 fields: {
  minutes: {
   type: "number",
   required: true
  },
  words: {
   type: "number",
   required: true
  }
 }
})

Add the ReadingTime Computed Field §

Next, open up the post definition file at /content/definitions/post.ts and define the new computed field.

import { defineDocumentType } from "contentlayer/source-files";
import GithubSlugger from "github-slugger";
import { Tag } from "./tag";
import { Series } from "./series";
import moment from "moment";
import readingTime from "reading-time";
import { ReadingTime } from "./reading-time";

export const Post = defineDocumentType(() => ({
  // .
  // .
  // .
  computedFields: {
    // .
    // .
    // .
    readingTime: {
      type: "nested",
      of: ReadingTime,
      resolve: (doc) => {
        const time = readingTime(doc.body.raw);
        return {
          minutes: Math.floor(time.minutes),
          words: time.words
        };
      },
    },
  },
}));

Now, we contentlayer generates the typesafe JSON, each post will have a new field called "readingTime" -- an object with two nested components; the minutes it takes to read, and the number of words.

You can then display in a component however you like, accessing those members as follows:

<ReadingTime
 minutes={post.readingTime?.minutes}
 words={post.readingTime?.words}
/>