I'm struggling to follow the read time recipe on Astro docs. I have tried to fix this multiple ways but can't seem to find a workaround solution to calculate the time taken to read.
astro.config.mjs
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
import { remarkReadingTime } from "./remark-reading-time";
import react from "@astrojs/react";
// https://astro.build/config
export default defineConfig({
site: "https://umma.dev",
integrations: [mdx(), sitemap(), react()],
markdown: {
remarkPlugins: [remarkReadingTime],
extendDefaultPlugins: true,
},
});
remark-reading.time.js
import getReadingTime from "reading-time";
import { toString } from "mdast-util-to-string";
/** Estimated Reading time */
export function remarkReadingTime() {
return function (tree, { data }) {
const textOnPage = toString(tree);
const readingTime = getReadingTime(textOnPage);
data.astro.frontmatter.estReadingTime = readingTime.minutes;
};
}
BlogPost.astro
---
import type { CollectionEntry } from "astro:content";
import BaseHead from "../components/BaseHead.astro";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import FormattedDate from "../components/FormattedDate.astro";
type Props = CollectionEntry<"blog">["data"];
const {
title,
description,
date,
updatedDate,
heroImage,
categories,
frontmatter,
} = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<style>
.title {
font-size: 2em;
margin: 0.25em 0 0;
}
hr {
border-top: 1px solid #ddd;
margin: 1rem 0;
}
.last-updated-on {
font-style: italic;
}
</style>
</head>
<body>
<Header />
<main>
<article>
{heroImage && <img width={720} height={360} src={heroImage} alt="" />}
<h1 class="title">{title}</h1>
<FormattedDate date={date} />
{
updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)
}
{categories}
{frontmatter?.estReadingTime}
<hr />
<slot />
</article>
</main>
<Footer />
</body>
</html>
Types are as follows:
import { defineCollection, z } from "astro:content";
const FrontmatterSchema = z.object({
estReadingTime: z.any(),
});
const blog = defineCollection({
schema: z.object({
title: z.string(),
// Transform string to Date object
date: z
.string()
.or(z.date())
.transform((val) => new Date(val)),
updatedDate: z
.string()
.optional()
.transform((str) => (str ? new Date(str) : undefined)),
heroImage: z.string().optional(),
categories: z
.enum([
"Front End",
"Back End",
])
.optional(),
notFound: z.string().optional(),
draft: z.boolean().optional(),
frontmatter: FrontmatterSchema.optional(),
}),
});
export const collections = { blog };
I can't seem to understand why this doesn't work? Is it because I'm using collections? Could anyone help with understanding why estReadingTime
is coming back as undefined?
Is there an easier way to do this in Astro rather than using the recipe or this specific plugin?
When using content collections, frontmatter provided by remark plugins is available from the return value of .render()
on a collection entry.
---
const entry = getEntry(...);
const { Content, remarkPluginFrontmatter } = await entry.render();
console.log(remarkPluginFrontmatter.estReadingTime)
---