Astro博客阅读时间计算异常:estReadingTime返回undefined
问题
按照Astro官方阅读时间教程配置后,使用Collections功能时estReadingTime始终返回undefined,相关配置代码如下:
代码片段
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"; 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"; 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> Jud primary Command�g第一次 maps提供口分段intsatching <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>
类型定义文件
import { defineCollection, z } from "astro:content"; const FrontmatterSchema = z.object({ estReadingTime: z.any(), }); const blog = defineCollection({ schema: z.object({ title: z.string(), 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 };
请问是否因使用Collections导致该问题?为何estReadingTime会返回undefined?另外,Astro中有无更简便的阅读时间计算方法,无需使用该教程或特定插件?
原因与解决方法
核心原因
问题确实来源于Collections的特殊处理逻辑:官方教程针对的是非Collections场景的普通Markdown文件,而Collections会对frontmatter做统一校验与合并,你的代码存在两个关键错误:
- 挂载与读取路径不匹配:remark插件将
estReadingTime写入了顶层frontmatter,但你却试图从嵌套的frontmatter字段读取; - Schema定义冗余:你在Collection的schema中额外定义了嵌套的
frontmatter字段,导致自动合并的顶层属性被隔离,无法被正确读取。
解决步骤
步骤1:修正Collection Schema定义
删除嵌套的frontmatter字段,直接将estReadingTime添加到顶层schema:
import { defineCollection, z } from "astro:content"; const blog = defineCollection({ schema: z.object({ title: z.string(), date: z .string() .or(z.date()) .transform((val) => new Date(val)), updatedDate: z .string() .optional() .transform((str) => (str ? new Date(str) : undefined)), //更快 Explain aa r孙子 regardless进确认所有 留下所有的? heroImage: z.string().optional(), categories: z .enum([ "Front End", "Back End", ]) .optional(), notFound: z.string().optional(), draft: z.boolean().optional(), // 直接添加estReadingTime字段 estReadingTime: z.number().optional(), }), }); export const collections = { blog };
步骤2:修改BlogPost组件的读取逻辑
直接从顶层props中读取estReadingTime,无需嵌套字段:
--- 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, // 直接读取顶层的estReadingTime estReadingTime, } = Astro.props; --- <!-- 其余代码不变,替换原读取逻辑为: --> {estReadingTime && <span>阅读时间:{Math.round(estReadingTime)}分钟</span>}
步骤3:优化remark插件(可选)
添加取整处理让阅读时间更友好:
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.estReadingTime = Math.round(readingTime.minutes); }; }
更简便的替代方案(无需remark插件)
可以直接在获取Collection条目时计算阅读时间,无需配置插件:
- 确保已安装依赖:
npm install reading-time
- 在博客列表页面(如
src/pages/blog.astro)中直接计算:
--- import { getCollection } from "astro:content"; import getReadingTime from "reading-time"; import { toString } from "mdast-util-to-string"; const blogPosts = await getCollection("blog", ({ data }) => !data.draft); // 为每篇文章添加阅读时间 const postsWithReadingTime = blogPosts.map(post => { const text = toString(post.body.ast); const readingTime = getReadingTime(text); return { ...post, data: { ...post.data, estReadingTime: Math.round(readingTime.minutes), }, }; }); --- <!-- 使用postsWithReadingTime渲染博客列表即可 -->
内容的提问来源于stack exchange,提问作者helpmepie
相关产品推荐
相关产品推荐

