withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
46.8k stars 2.49k forks source link

Zod `.default()` gets removed from Markdown frontmatter props #12057

Open xavdid opened 1 month ago

xavdid commented 1 month ago

Astro Info

Astro                    v4.15.8
Node                     v18.20.3
System                   Linux (x64)
Package Manager          unknown
Output                   static
Adapter                  none
Integrations             @astrojs/mdx
                         @astrojs/sitemap

If this issue only occurs in one browser, which browser is a problem?

N/A

Describe the Bug

When declaring a zod schema with a default value, that default isn't used when parsing mdx frontmatter. For example:

const blog = defineCollection({
  type: 'content',
  // Type-check frontmatter using a schema
  schema: z.object({
    title: z.string(),
    list: z.array(z.string()).default([]),
  }),
});

And this post:

---
title: 'missing list post'
---

I'd expect to see an empty list in props:

<pre>{JSON.stringify(frontmatter, null, 2)}</pre>

Correctly passes validation, but doesn't pick up the default []:

What's the expected result?

I'd expect the post to show zod's default value if a prop isn't passed:

{
  "title": "broken post",
  "list": []
}

Link to Minimal Reproducible Example

Participation

ascorbic commented 1 month ago

Hi. Currently the frontmatter variable is the raw frontmatter, without parsing. I do think it would be better if it were the parsed data, but there is a workaround. The post.data has the correct default, and you can pass it as a prop to <Content />:

---
import { type CollectionEntry, getCollection } from "astro:content";
import BlogPost from "../../layouts/BlogPost.astro";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: post,
  }));
}
type Props = CollectionEntry<"blog">;

const post = Astro.props;
const { Content } = await post.render();
---

<BlogPost {...post.data}>
  <Content data={post.data} />
</BlogPost>

Then you can access this as props.data in MDX:

---
title: 'broken post'
---

I'd expect to see an empty list in props:

<pre>{JSON.stringify(props.data, null, 2)}</pre>
xavdid commented 1 month ago

That's fair, that works as a workaround! I think clearer docs around how to read frontmatter from within a post would be useful (especially for this edge case - it was really confusing that title worked but list didn't.

Thanks!