--- title: How to add an estimated reading time in AstroPaper author: Sat Naing pubDatetime: 2023-07-21T10:11:06.130Z modDatetime: 2024-01-03T14:53:25Z slug: how-to-add-estimated-reading-time featured: false draft: false tags: - FAQ description: How you can add an 'Estimated Reading time' in your blog posts of AstroPaper. --- As the [Astro docs](https://docs.astro.build/en/recipes/reading-time/) say, we can use remark plugin to add a reading time property in our frontmatter. However, for some reason, we can't add this feature by following what stated in Astro docs. Therefore, to achieve this, we have to tweak a little bit. This post will demonstrate how we can do that. ## Table of contents ## Add reading time in PostDetails Step (1) Install required dependencies. ```bash npm install reading-time mdast-util-to-string ``` Step (2) Create `remark-reading-time.mjs` file under `utils` directory ```js import getReadingTime from "reading-time"; import { toString } from "mdast-util-to-string"; export function remarkReadingTime() { return function (tree, { data }) { const textOnPage = toString(tree); const readingTime = getReadingTime(textOnPage); data.astro.frontmatter.readingTime = readingTime.text; }; } ``` Step (3) Add the plugin to `astro.config.ts` ```js import { remarkReadingTime } from "./src/utils/remark-reading-time.mjs"; // make sure your relative path is correct // https://astro.build/config export default defineConfig({ site: SITE.website, integrations: [ // other integrations ], markdown: { remarkPlugins: [ remarkToc, remarkReadingTime, // 👈🏻 our plugin [ remarkCollapse, { test: "Table of contents", }, ], ], // other config }, // other config }); ``` Step (4) Add `readingTime` to blog schema (`src/content/config.ts`) ```ts import { SITE } from "@config"; import { defineCollection, z } from "astro:content"; const blog = defineCollection({ type: "content", schema: ({ image }) => z.object({ // others... canonicalURL: z.string().optional(), readingTime: z.string().optional(), // 👈🏻 readingTime frontmatter }), }); export const collections = { blog }; ``` Step (5) Create a new file called `getPostsWithRT.ts` under `src/utils` directory. ```ts import type { MarkdownInstance } from "astro"; import type { CollectionEntry } from "astro:content"; import { slugifyStr } from "./slugify"; export const getReadingTime = async () => { // Get all posts using glob. This is to get the updated frontmatter const globPosts = import.meta.glob("../content/blog/*.md") as Promise< CollectionEntry<"blog">["data"][] >; // Then, set those frontmatter value in a JS Map with key value pair const mapFrontmatter = new Map(); const globPostsValues = Object.values(globPosts); await Promise.all( globPostsValues.map(async globPost => { const { frontmatter } = await globPost(); mapFrontmatter.set( slugifyStr(frontmatter.title), frontmatter.readingTime ); }) ); return mapFrontmatter; }; const getPostsWithRT = async (posts: CollectionEntry<"blog">[]) => { const mapFrontmatter = await getReadingTime(); return posts.map(post => { post.data.readingTime = mapFrontmatter.get(slugifyStr(post.data.title)); return post; }); }; export default getPostsWithRT; ``` Step (6) Refactor `getStaticPaths` of `/src/pages/posts/[slug].astro` as the following ```ts --- // other imports import getPostsWithRT from "@utils/getPostsWithRT"; export interface Props { post: CollectionEntry<"blog">; } export async function getStaticPaths() { const posts = await getCollection("blog", ({ data }) => !data.draft); const postsWithRT = await getPostsWithRT(posts); // replace reading time logic with this func const postResult = postsWithRT.map(post => ({ // make sure to replace posts with postsWithRT params: { slug: post.slug }, props: { post }, })); // other codes ``` Step (7) Refactor `PostDetails.astro` like this. Now you can access and display `readingTime` in `PostDetails.astro` ```ts --- // imports export interface Props { post: CollectionEntry<"blog">; } const { post } = Astro.props; const { title, author, description, ogImage, readingTime, // we can now directly access readingTime from frontmatter pubDatetime, modDatetime, tags } = post.data; // other codes --- ``` ## Access reading time outside of PostDetails (optional) By following the previous steps, you can now access `readingTime` frontmatter property in you post details page. Sometimes, this is exactly what you want. If so, you can skip to the next section. However, if you want to display "estimated reading time" in index, posts, and technically everywhere, you need to do the following extra steps. Step (1) Update `utils/getSortedPosts.ts` as the following ```ts import type { CollectionEntry } from "astro:content"; import getPostsWithRT from "./getPostsWithRT"; const getSortedPosts = async (posts: CollectionEntry<"blog">[]) => { // make sure that this func is async const postsWithRT = await getPostsWithRT(posts); // add reading time return postsWithRT .filter(({ data }) => !data.draft) .sort( (a, b) => Math.floor( new Date(b.data.modDatetime ?? b.data.pubDatetime).getTime() / 1000 ) - Math.floor( new Date(a.data.modDatetime ?? a.data.pubDatetime).getTime() / 1000 ) ); }; export default getSortedPosts; ``` Step (2) Make sure to refactor every file which uses `getSortedPosts` function. You can simply add `await` keyword in front of `getSortedPosts` function. Files that use `getSortedPosts` function are as follow - src/pages/index.astro - src/pages/posts/index.astro - src/pages/rss.xml.ts - src/pages/posts/index.astro - src/pages/posts/[slug].astro - src/utils/getPostsByTag.ts All you have to do is like this ```ts const sortedPosts = getSortedPosts(posts); // old code ❌ const sortedPosts = await getSortedPosts(posts); // new code ✅ ``` Now you can access `readingTime` in other places besides `PostDetails` ## Displaying reading time (optional) Since you can now access `readingTime` in your post details (or everywhere if you do the above section), it's up to you to display `readingTime` wherever you want. But in this section, I'm gonna show you how I would display `readingTime` in my components. This is optional. You can ignore this section if you want. Step (1) Update `Datetime` component to display `readingTime` ```tsx import { LOCALE } from "@config"; export interface Props { datetime: string | Date; size?: "sm" | "lg"; className?: string; readingTime?: string; // new type } export default function Datetime({ datetime, size = "sm", className, readingTime, // new prop }: Props) { return ( // other codes ({readingTime}) {/* display reading time */} // other codes ); } ``` Step (2) Then, pass `readingTime` props from its parent component. file: Card.tsx ```ts export default function Card({ href, frontmatter, secHeading = true }: Props) { const { title, pubDatetime, modDatetime description, readingTime } = frontmatter; return ( ... ... ); } ``` file: PostDetails.tsx ```jsx // Other Codes

{title}

{/* Other Codes */}
// Other Codes ``` ## Conclusion By following the provided steps and tweaks, you can now incorporate this useful feature into your content. I hope this post helps you adding `readingTime` in your blog. AstroPaper might include reading time by default in future releases. 🤷🏻‍♂️ Kyay Zuu for Reading 🙏🏻