Raunaq Pahwa
CS Grad at SBU, NY

Get accurate read times for MDX files in Astro

May 26, 2024 (3mo ago)

253 views

2 min read

When I checked the word count of my first post, I noticed that the number of words in the content was not the same as was output from the plugins recommended by Astro.

The Astro docs suggest installing two plugins to calculate the read time/word count- mdast-util-to-string and reading-time.

If you’re using MDX, and importing or exporting custom components, the mdast-util-to-string plugin includes the import and export statements which is something you probably don’t want as part of the read time.

To remove the ES Module imports and exports, we can filter those nodes in the MDAST(Markdown Abstract Syntax Tree) before converting it to a string.

Moreover, the plugin merges elements together without any spaces. This means that a word at the end of one paragraph gets combined with the first word of the next paragraph, reducing the overall word count by the number of top-level blocks in your MDX file.

To fix this issue, you can iterate over the last child of each top-level element and manually add a space in the cloned markdown syntax tree.

Note- Do not change the original tree or do a shallow copy as the changes are performed in-place. Astro won’t be able to recognize your imports/exports and the spaces will be added to the DOM nodes.

import getReadingTime from "reading-time";
import { toString } from "mdast-util-to-string";

export function remarkReadingTime() {
  return function (tree, { data }) {
    // Create a deep clone
    const readingTree = structuredClone(tree);
    // Remove ESM statements
    readingTree.children = readingTree.children.filter(
      (node) => node.type !== "mdxjsEsm"
    );
    readingTree.children.forEach((node) => {
      // Add a space if it's a value node
      if ("value" in node) {
        node.value += " ";
      } else if ("children" in node) {
        // Traverse to the last child and add a space 
        let parent = node;
        let child = node.children.at(-1);
        while (child) {
          parent = child;
          child = child.children?.at(-1);
        }
        if (parent && "value" in parent) {
          parent.value += " ";
        }
      }
    });
    const textOnPage = toString(readingTree);
    const readingTime = getReadingTime(textOnPage);
    data.astro.frontmatter.minutesRead = readingTime.text;
  };
}