こうこく
作 ▸

Astro5.13 MDXのレンダリング結果をHTML文字列として取得する

いま別件でまたAstro製のブログを作ってて、諸事情により途中で記事ファイルをMarkdown -> MDXに変えたんだけど、そしたらもともとパラメータでHTML文字列を受け取るつもりだったコンポーネントが動かせなくなった。

AstroでMDXをレンダリングする時って render() を使うけど、その戻り値はAstroのコンポーネントになるから、そのままじゃHTML文字列としては扱えない。それをHTML文字列に変換する方法を調べたのでメモ。

astro 5.13.3@astrojs/mdx 4.3.6

結論だけ知りたい人は最後の項だけ読んでください。

Markdownのレンダリング結果をHTMLとして取得する場合

まず最初に、MarkdownのコンテンツをHTMLとして取得して、レンダリングするコードはこんな感じ。

---
import { getEntry, render } from 'astro:content';

// ↓このコンテンツは *.md です
const id = '20251012-retrieve-the-mdx-rendering-result-as-an-html-string-in-astro-5.13';
const entry = await getEntry('blog', id);
if (!entry) {
  throw new Error(`Entry not found: ${id}`);
}
---
<p>Published on: {entry.data.published.toDateString()}</p>
<Fragment set:html={entry.body.rendered?.html} />

AstroはFragmentという組み込みコンポーネントを持っていて、属性が無いなら <></> で使えるけど、ここみたいに set:html 属性を指定する場合は <Fragment ... /> と書く。

MDXを普通にレンダリングする場合

次に、MDXをHTMLにせず普通にレンダリングするコードはこんな感じ。いや、上記ドキュメントのサンプルとほぼ同じだけど。

---
import { getEntry, render } from 'astro:content';

// ↓このコンテンツは *.mdx です
const id = '20251012-retrieve-the-mdx-rendering-result-as-an-html-string-in-astro-5.13';
const entry = await getEntry('blog', id);
if (!entry) {
  throw new Error(`Entry not found: ${id}`);
}
const { Content, headings } = await render(entry);
---
<p>Published on: {entry.data.published.toDateString()}</p>
<Content />

こちらはMarkdownとは違って、 entry.body.renderedundefined になってしまう。なお、このときコンソールに entry を出力してみると deferredRender: true というプロパティが追加されている。

上記の Content はAstroのコンポーネントオブジェクト (型は AstroComponentFactory) で、べつに他のAstroコンポーネントのpropsとしても使えるんだけど、今回はHTML文字列として取得したい。

MDXのレンダリング結果をHTMLとして取得する場合

MDXを render() した戻りの Content の内容をHTML文字列として取得したい場合は、Astro Container API (experimental)を使う。

Astro Container APIはexperimentalな機能なので将来のアップデートで変更される可能性があるけど、これを使うと、Astroコンポーネントを個別にレンダリングできるらしい。

---
import { getContainerRenderer as mdxContainerRenderer } from '@astrojs/mdx';
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { loadRenderers } from 'astro:container';
import { getEntry, render } from 'astro:content';

const renderers = await loadRenderers([mdxContainerRenderer()]);
const container = await AstroContainer.create({ renderers });

// ↓このコンテンツは *.mdx です
const id = '20251012-retrieve-the-mdx-rendering-result-as-an-html-string-in-astro-5.13';
const entry = await getEntry('blog', id);
if (!entry) {
  throw new Error(`Entry not found: ${id}`);
}
const { Content, headings } = await render(entry);
const html = await container.renderToString(Content);
---
<p>Published on: {entry.data.published.toDateString()}</p>
<Fragment set:html={html} />

この renderToString() メソッドにAstroコンポーネントである Content を渡すと、HTML文字列が返ってくる。ここではそれを Fragmentset:html 属性に渡して、HTMLとしてレンダリングしている。

以上。なんか意外と、 *.md をただ *.mdx に変えただけじゃ動かないところがあって、面倒だなと思った。

この記事に何かあればこちらまで (非公開)