Skip to content
qino

Since 0.1.0

getMarkdownStats

Word and character counts for a Markdown or MDX body.

Counts readable prose in a Markdown or MDX string. Exported from @qino/cms/utils.

src/lib/reading-time.ts
import { getMarkdownStats } from "@qino/cms/utils";

const stats = getMarkdownStats(post.markdown);
const readingMinutes = Math.ceil(stats.wordCount / 220);

Signature

@qino/cms/utils
function getMarkdownStats(body: string): MarkdownStats;

Parameters

NameTypeRequiredDescription
bodystringyesRaw Markdown or MDX source, typically entry.markdown

Returns

A MarkdownStats object:

FieldMeaning
wordCountWord-like segments in the prose
proseCharacterCountGraphemes in the prose after whitespace collapsing
sourceCharacterCountGraphemes in the untouched input

What counts as prose

The body is parsed with remark, GFM, and MDX. Text nodes are kept. Code blocks, inline code, HTML, MDX expressions, MDX components, and import or export statements are excluded. Link and image URLs are excluded. Line breaks become newlines and block nodes are joined with newlines, then whitespace is collapsed.

Words are segmented with Intl.Segmenter, and characters are counted as graphemes rather than UTF-16 code units, so multi-byte scripts and emoji count once each.

Throws

Nothing. If MDX parsing fails, such as on an HTML comment, the body is re-parsed as plain Markdown.

Example

Reading time as an augment field:

qino/collections/posts.ts
views: (view) => ({
  default: view({
    augment: (post) => {
      const stats = getMarkdownStats(post.markdown);
      return { ...stats, readingMinutes: Math.ceil(stats.wordCount / 220) };
    },
  }),
}),