docs
docs copied to clipboard
[md/mdx] components export for `mdx` in integration guide
One thing I really like about MDX is that you can pass custom components for default html elements (h1/6, img, a, code, pre, etc.) with export const components = {}. The names comes from the Table of Components for MDX. Much easier than writing remark/rehype plugins IMO.
There's an example of it here, but could be nice to highlight in the docs: https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-mdx?file=src%2Fpages%2Findex.mdx
@bholmesdev, Would you welcome an additional section in the MDX integration guide? I was told that you were working on those docs, so I don't want to interfere or do work you may already have done!
Adding to this, you can also do this after importing MDX:
---
import Content from '../content.mdx';
import Heading from '../Heading.astro';
---
<Content components={{ h1: Heading }} />
This is particularly useful in a dynamic route where you can glob all your .mdx
content and render each to its own page while passing in default replacement components. (This is how I handled custom image components on the new astro.build site.)
---
// src/pages/[slug].astro
import path from "node:path";
import Layout from "../layouts/Layout.astro";
import Heading from "../components/Heading.astro";
export async function getStaticPaths() {
const posts = await Astro.glob('../content/*.mdx');
return posts.map(post => ({
params: { slug: path.parse(post.file).name },
props: post,
}));
}
---
<Layout>
<Astro.props.default components={{ h1: Heading }} />
</Layout>
Thanks @kylebutts, @delucis! This sounds useful, and I'd absolutely want @bholmesdev's input on this.
We are actively (continuously? 😅) working on our MD/MDX documentation right now, including some reorganizing of content, so there is stuff happening all the time, and yes, I think adding halpful patterns is right up our alley at the moment!
@sarah11918 I totally agree we should document this, especially @delucis' components-as-props snippet above. We've gotten enough support questions on component rendering that warrants some docs. This could slide into the integration README for now, but I know we've talked about "proper" MDX docs separate from the README too. Worth aligning there.
Want me to add a draft gist @sarah11918?
@kylebutts That would be great! Do your worst! Thanks for offering. 🧑🚀
I'm not sure where this would go, maybe after Layouts in the mdx integration?
Custom components
Under the hood, MDX will convert markdown into html components. For example,
> A blockquote with *some* emphasis.
will be converted into
<blockquote>
<p>A blockquote with <em>some</em> emphasis.</p>
</blockquote>
MDX provides a way to tap into this rendering and use your own custom components. In the above example, you could create a custom Blockquote component (in any language) that has either a <slot />
component or accepts a children
prop. Then in the MDX file you import the component and export it to the components
export.
import Blockquote from '../components/Blockquote.astro';
export const components = { blockquote: Blockquote };
The advantage of this is it allows for the simplicity of writing in markdown without having to write the custom component or writing a remark/rehype plugin. A full list of components that can have custom components is on the [MDX website]https://mdxjs.com/table-of-components/).
Custom components with imported mdx
Custom components can also be passed to the components prop when rending imported MDX content.
---
import Content from '../content.mdx';
import Heading from '../Heading.astro';
---
<Content components={{ h1: Heading }} />
This is particularly useful in a dynamic route where you can glob all your .mdx content and render each to its own page while passing in default replacement components.
---
// src/pages/[slug].astro
import path from "node:path";
import Layout from "../layouts/Layout.astro";
import Heading from "../components/Heading.astro";
export async function getStaticPaths() {
const posts = await Astro.glob('../content/*.mdx');
return posts.map(post => ({
params: { slug: path.parse(post.file).name },
props: post,
}));
}
---
<Layout>
<Astro.props.default components={{ h1: Heading }} />
</Layout>
@kylebutts Couldn't have written this section better myself 🤩 My only critique is the "This is particularly useful in a dynamic route..." concluding section feels pretty advanced. I might want to keep that part out, and put the rest just after the "layouts" section as you suggested. Happy to review if you submit a PR for this!
(Reminder to self that this has been PR'd in withastro/astro
!)
Closing as withastro/astro#4530 has been merged 🥳
Thank you for the documentation.
I wondered if there is a way how to set the component overrides globally?
I would like to automatically replace <img />
tags with <Image />
in my mdx files.
Hi @jtomek! The best place to ask this is in our Discord: https://astro.build/chat
If there isn't documentation that answers your question on this page: https://docs.astro.build/en/guides/integrations-guide/mdx/ or in MDX's documentation itself, then asking in our support-threads
channel is the best place to have this discussion!
Hello @sarah11918 , thank you. I will ask there.
I wondered if there is a way how to set the component overrides globally?
@jtomek I think the closest we have at the moment is what I describe above in my comment — importing all your MDX files and then rendering them by passing in the component overrides.
But as @sarah11918 says, Discord would be a great way to ask others and chat about solutions. Or otherwise a discussion in https://github.com/withastro/rfcs would be the place to start thinking of ways to support this.
I think it would be a natural extension to include custom MDX components in the layout component. I can make an RFC for this if folks agree?
Definitely worth at least a discussion in the RFCs repo!