· astro / mdx / content-collections
Cách thêm MDX vào Astro 5 Content Collections
Hướng dẫn cài đặt @astrojs/mdx và kết nối với Content Layer API của Astro 5 — frontmatter có kiểu dữ liệu, import component, và các lỗi thường gặp.
Bởi Ethan
1.147 từ · 6 phút đọc
Nếu bạn muốn nhúng React component tương tác vào các bài viết trên blog Astro, bạn cần MDX. Đây là cách thiết lập chính xác cho Astro 5 — bao gồm cấu hình Content Layer API đã thay thế cách tiếp cận cũ từ v2/v4.
Dành cho ai
Các developer đang dùng Astro 5 với Content Layer API và muốn nhúng component vào bài viết markdown. Nếu bạn vẫn đang dùng Astro v2 hoặc v3, bạn cần migrate src/content/config.ts trước — xem hướng dẫn nâng cấp lên Astro v5 trước khi tiếp tục. Nếu bạn đã dùng Astro 6, các bước dưới đây vẫn áp dụng được; xem bài review Astro 6 của chúng tôi để biết thêm những gì thay đổi trong bản đó.
Môi trường thử nghiệm
- Astro: 5.18.2
- @astrojs/mdx: 4.3.14
- Node: 20.x, macOS
Bước 1: Cài đặt MDX integration
Cách nhanh nhất là dùng Astro CLI:
npx astro add mdx
Lệnh này cài @astrojs/mdx@^4 và tự động thêm integration vào astro.config.mjs.
Cài thủ công nếu bạn muốn kiểm soát rõ ràng hơn:
npm install @astrojs/mdx@^4
# hoặc: pnpm add @astrojs/mdx@^4
# hoặc: bun add @astrojs/mdx@^4
Sau đó thêm vào config:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [mdx()],
});
Lỗi thường gặp: nếu astro.config.mjs đang có lỗi cú pháp khi bạn chạy npx astro add mdx, CLI sẽ báo thành công nhưng thực ra bỏ qua việc inject integration. Hãy mở file sau đó và xác nhận mdx() đã xuất hiện trong integrations.
Bước 2: Cấu hình src/content.config.ts
Astro 5 đã chuyển config collection từ src/content/config.ts (đường dẫn cũ của v2/v4) sang src/content.config.ts ở thư mục gốc của src/. Các collection giờ bắt buộc phải có thuộc tính loader: — thiếu nó sẽ gây ra lỗi Zod validation khó hiểu lúc build.
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({
base: './src/content/blog',
pattern: '**/*.{md,mdx}', // chấp nhận cả md lẫn mdx
}),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
z.coerce.date() quan trọng ở đây: ngày trong frontmatter YAML được đọc dưới dạng string, và coerce xử lý việc chuyển đổi string thành Date mà không cần custom transform.
Lỗi thường gặp: giữ nguyên đường dẫn cũ src/content/config.ts khiến Astro âm thầm bỏ qua schema của bạn — build vẫn thành công, nhưng frontmatter không có kiểu dữ liệu.
Bước 3: Viết file .mdx
Đặt một file .mdx bất kỳ trong đường dẫn glob và import component trực tiếp trong phần nội dung:
---
title: "Hello MDX"
description: "A post with an inline callout component."
pubDate: 2025-06-01
---
import Callout from '../../components/Callout.astro';
Bài viết này nhúng component trực tiếp:
<Callout type="warning">
Tính năng này hiện đang trong giai đoạn beta.
</Callout>
Tiếp tục đọc...
Để tránh rủi ro khi di chuyển bài viết vào thư mục con, hãy dùng path alias đã định nghĩa trong tsconfig.json (@/components/Callout.astro) thay vì relative import — relative path sẽ bị gãy khi bạn di chuyển file.
Bước 4: Render trong [...slug].astro
Có hai thay đổi trong Astro v5 dễ bị bỏ sót: render() giờ là import độc lập từ astro:content (không còn là method trên post object nữa), và dynamic segment nên dùng post.id thay vì post.slug.
---
// src/pages/blog/[...slug].astro
import { getCollection, render } from 'astro:content';
import Callout from '../../components/Callout.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 } = await render(post);
---
<html>
<body>
<h1>{post.data.title}</h1>
<!-- truyền Callout vào để mọi file MDX có thể dùng mà không cần import tường minh -->
<Content components={{ Callout }} />
</body>
</html>
Truyền components vào <Content /> là tùy chọn — nó ánh xạ tên component sang implementation cụ thể để các file MDX có thể dùng <Callout> mà không cần import trực tiếp. Hữu ích khi bạn có một component từ design system mà mọi bài viết đều dùng.
Các lỗi thường gặp
- Đường dẫn config đã thay đổi:
src/content/config.ts→src/content.config.ts(nằm ở gốcsrc/, không phải trongcontent/). Đường dẫn cũ bị bỏ qua âm thầm — không có build error, không có type safety. slugđổi tên thànhid:post.slugtrả vềundefinedtrong Astro v5. Dùngpost.idở khắp nơi, kể cả tronggetStaticPaths().render()là import độc lập:await post.render()(v4) →import { render } from 'astro:content'; await render(post)(v5).- HTML comment gây lỗi parse: MDX là JSX.
<!-- comment -->là lỗi cú pháp. Dùng{/* comment */}thay thế. - File bắt đầu bằng underscore không còn bị tự động loại trừ:
_draft.mdxbị bỏ qua âm thầm trong v4. Trong v5 nó được đưa vào collection. Hãy lọc tường minh bằngdraft: truetrong frontmatter vàgetCollection('blog', ({ data }) => !data.draft). - Thứ tự Remark plugin:
@astrojs/mdxkế thừamarkdown.remarkPluginstheo mặc định. Nếu một plugin giả định nó chạy cuối cùng, nó có thể thấy các MDX node chưa được xử lý. ĐặtextendMarkdownConfig: falsetrong tùy chọn MDX integration và khai báo tường minh tất cả plugin.
Cloudflare Pages
MDX được xử lý hoàn toàn lúc build. Khi bạn chạy astro build, mọi file .mdx được biên dịch thành HTML và JavaScript tĩnh trước khi deploy. Cloudflare Pages serve output đó — không cần cấu hình adapter thêm cho MDX.
SSR với @astrojs/cloudflare không bị ảnh hưởng: MDX collection luôn được prerender bất kể adapter. Nếu build của bạn chạy thành công cục bộ với astro build, Cloudflare Pages sẽ serve đúng. Xem hướng dẫn deploy đầy đủ tại Cách deploy site Astro lên Cloudflare Pages.
Nhận xét
Thêm MDX khi bài viết cần nhúng UI tương tác — code playground, live demo, hoặc design-system component như callout hay warning. Nếu bài viết là nội dung thuần văn bản, .md build nhanh hơn, dễ bàn giao hơn cho tác giả không phải developer, và dễ chuyển sang framework khác hơn.
MDX thêm một bước biên dịch JSX cho mỗi file. Với 50–100 bài viết, overhead build không đáng kể trong thực tế. Với 1.000+ file, hãy bật optimize: true trong config của MDX integration — Astro docs đánh dấu đây là tùy chọn được khuyến nghị cho collection lớn.
Nếu hướng dẫn này là một phần trong quá trình xây dựng site đa ngôn ngữ, bước tiếp theo tự nhiên là Cách xây dựng site Astro đa ngôn ngữ.