Rendering Markdown
ใน Next.js พร้อม contentlayer คุณ import MDX component โดยตรง ใน Astro คุณเรียก render(entry) (API เดียวตั้งแต่ Astro 5 — method entry.render() แบบเก่าถูกถอดออกไปตอน entry กลายเป็น plain serializable object) เพื่อรับ Content component แล้วใช้ <Content /> ใน template ฟิลด์ frontmatter อยู่ที่ entry.data ส่วนเนื้อหาที่ render แล้วมาจาก Content
ต้องใช้โปรเจกต์ Astro เต็มรูปแบบ ใช้
npm create astro@latestเพื่อสร้างโปรเจกต์
Rendering an entry: contentlayer vs Astro
หัวข้อที่มีชื่อว่า “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].astroimport { 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(entry) คืนค่า { Content, headings, remarkPluginFrontmatter } — Content เป็น component ที่ render ได้โดยตรง ไม่มี hook ไม่มีการ compile MDX ฝั่ง client
Full blog post page
หัวข้อที่มีชื่อว่า “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.id }, 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
หัวข้อที่มีชื่อว่า “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.id}/`}>{post.data.title}</a> </li> ))}</ul>Markdown pipeline: Sätteri (Astro 7)
หัวข้อที่มีชื่อว่า “Markdown pipeline: Sätteri (Astro 7)”Astro 7 render ไฟล์ .md และ .mdx ด้วย Sätteri ซึ่งเป็น Markdown pipeline แบบ native ที่เขียนด้วย Rust แทน pipeline remark/rehype (unified) แบบเดิม Content Collections, render(entry), headings และ frontmatter ทำงานเหมือนเดิมทุกอย่าง แค่ build เร็วขึ้น (Astro รายงานว่า build เร็วขึ้นโดยรวม 15–61% จาก benchmark เว็บไซต์จริงของทีม Astro) สองเรื่องที่ควรรู้ตอนย้ายโปรเจกต์ Next.js/contentlayer:
- Markdown ธรรมดา, ตาราง GFM, code fence กับ Shiki และ heading ID ไม่ต้องตั้งค่าอะไรเพิ่ม
- ถ้าพึ่งพา remark/rehype plugin แบบ custom (เช่น
rehype-autolink-headings) ให้ opt-in กลับไปใช้ unified pipeline อย่างชัดเจน:
import { defineConfig } from 'astro/config';import { unified } from '@astrojs/markdown-remark';
export default defineConfig({ markdown: { processor: unified(), // remark/rehype pipeline rehypePlugins: [/* ... */], },});MDX ยังผ่าน @astrojs/mdx เหมือนเดิม สิ่งที่เปลี่ยนคือ processor ค่าเริ่มต้นของเนื้อหา Markdown เท่านั้น