From an Entry to Actual HTML on the Page
Rendering Content
An entry's body is raw Markdown/MDX. `render(entry)` returns a `<Content />` component you can drop into JSX.
What you'll learn
- Render Markdown/MDX content
- Pass components into MDX
- Read the table of contents (`headings`)
A collection entry’s body is raw text. To render it as HTML, use
the render() helper.
The Pattern
---
// src/pages/blog/[slug].astro
import { getCollection, render } from "astro:content";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content, headings } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article> render(post) returns:
Content— a component that renders the parsed Markdown/MDXheadings— an array of{ depth, slug, text }for building a table of contentsremarkPluginFrontmatter— frontmatter after remark plugins have run
Building a Table of Contents
---
const { Content, headings } = await render(post);
---
<nav>
<ul>
{headings.map(h => (
<li class:list={[`toc-depth-${h.depth}`]}>
<a href={`#${h.slug}`}>{h.text}</a>
</li>
))}
</ul>
</nav>
<article>
<Content />
</article> Passing Components to MDX
For MDX entries, you can supply components to replace default elements:
---
import Callout from "../components/Callout.astro";
const { Content } = await render(post);
---
<Content components={{ Callout }} /> Now any <Callout> used inside the .mdx body resolves to your
component without needing an import in every file.
When You Don’t Need render
If you only need frontmatter on an index page, skip render() —
you don’t need the parsed body for listings.
Chapter Done
That wraps content collections. Next chapter steps OUT of pure static — adding interactivity via islands.
Islands Intro →