Rendering Markdown
In Next.js with contentlayer you import the MDX component directly. In Astro you call render(entry) (Astro 5+) to get a Content component, then drop <Content /> into your template. The frontmatter fields live on entry.data, the rendered body comes from Content.
Needs a full Astro project to run. Use
npm create astro@latestto scaffold one.
Rendering an entry: contentlayer vs Astro
Section titled “Rendering an entry: contentlayer vs Astro”// app/blog/[slug]/page.tsx (Next.js App Router + contentlayer)import { allPosts } from 'contentlayer/generated';import { useMDXComponent } from 'next-contentlayer/hooks';
export default function PostPage({ params }) { const post = allPosts.find(p => p.slug === params.slug); const MDXContent = useMDXComponent(post.body.code);
return ( <article> <h1>{post.title}</h1> <MDXContent /> </article> );}---// src/pages/blog/[slug].astro (Astro 5+)import { getCollection, render } from 'astro:content';
export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(post => ({ params: { slug: post.slug }, props: { post } }));}
const { post } = Astro.props;const { Content, headings } = await render(post);---<article> <h1>{post.data.title}</h1> <Content /></article>render(entry) returns { Content, headings, remarkPluginFrontmatter }. Content is a component you render directly — no hooks, no client-side MDX compilation.
Full blog post page
Section titled “Full blog post page”---import { getCollection, render } from 'astro:content';import BaseLayout from '../../layouts/BaseLayout.astro';
export async function getStaticPaths() { const posts = await getCollection('blog', ({ data }) => !data.draft); return posts.map((post) => ({ params: { slug: post.slug }, props: { post }, }));}
const { post } = Astro.props;const { Content, headings } = await render(post);---<BaseLayout title={post.data.title}> <article> <header> <h1>{post.data.title}</h1> <time datetime={post.data.date.toISOString()}> {post.data.date.toLocaleDateString('en-US', { dateStyle: 'long' })} </time> </header> <Content /> </article></BaseLayout>Listing entries
Section titled “Listing entries”// app/blog/page.tsx (Next.js App Router + contentlayer)import { allPosts } from 'contentlayer/generated';
export default function BlogIndex() { const sorted = allPosts.sort( (a, b) => new Date(b.date) - new Date(a.date) ); return ( <ul> {sorted.map(post => ( <li key={post.slug}> <a href={post.url}>{post.title}</a> </li> ))} </ul> );}---// src/pages/blog/index.astroimport { getCollection } from 'astro:content';
const posts = await getCollection('blog', ({ data }) => !data.draft);const sorted = posts.sort( (a, b) => b.data.date.valueOf() - a.data.date.valueOf());---<ul> {sorted.map((post) => ( <li> <a href={`/blog/${post.slug}/`}>{post.data.title}</a> </li> ))}</ul>