AstroブログにQA機能を追加!FAQコンポーネント実装

ブログを書く際に、以下のような QA コンテンツを使いたくなる場面はないでしょうか?
自分自身 Astro でブログを書いている中で、QA コンテンツを使いたくなる場面がありました。しかし、デフォルトの Markdown の記法の中で上記のような QA コンテンツを表現する記法がなかったため独自の QA 記法を追加するために mdx で使えるコンポーネントを作成しました
本記事では、その QA コンポーネントについてまとめています
コンポーネントではなくプラグインで QA コンテンツを表示する方法も別途解説しているので、興味があれば読んでみてください!

mdx でコンポーネントを使う方法
Astro では mdx インテグレーションをインストールすると Astro コンポーネントや UI フレームワークコンポーネントを mdx ファイルで使用できるようになります。こちらについてはここでは詳しくは説明しないので、適宜 Astro Docs を参照してください
mdx ファイルにコンポーネントをインポートする方法はいくつかあって、まず公式 Doc で説明されている方法は各 mdx ファイルでコンポーネントをインポートして利用する方法です
---
layout: ../layouts/BaseLayout.astro
title: About me
---
import Button from '../components/Button.astro';
import ReactCounter from '../components/ReactCounter.jsx';
私は**火星**に住んでいますが、気軽に<Button title="お問い合わせ" />までご連絡ください。
以下は、MDXで動作するカウンターコンポーネントです。
<ReactCounter client:load />
しかし、この方法では mdx ファイルを作成するたびにコンポーネントをインポートしなければならず、少し手間です
そこで、次の選択肢に上がってくるのがコンテンツがレンダリングされる際に生成される <Content />
コンポーネントの引数にコンポーネントを渡す方法です。詳しくは Astro Docs を見ていただきたいのですが、この方法を使えば各 mdx ファイルでコンポーネントをインポートすることなくコンポーネントを使うことができるようになります
---
import { getEntry } from 'astro:content';
const entry = await getEntry('blog', 'blog-1');
const { Content, headings } = await entry.render();
import Toc from "./components/Toc.astro";
---
<p>公開日: {entry.data.published.toDateString()}</p>
<Content components={{ Toc }} />
上記の方法でも良いと思いますが、個人的にお勧めしたいのは astro-auto-import という Astro のインテグレーションを使用する方法です。このインテグレーションを使うと、コンポーネントや他のモジュールを自動インポートしてくれるので、インポートせずに mdx ファイルでコンポーネントにアクセスできるようになります
設定は簡単で astro-auto-import をプロジェクトにインストールして astro.config.mjs ファイルで以下のように AutoImport をインポートして設定に追加するだけです。自分自身は imports オプションにファイル名を渡す運用をしています!
$ yarn add astro-auto-import
import { defineConfig } from 'astro/config';
import AutoImport from 'astro-auto-import';
import mdx from '@astrojs/mdx';
export default defineConfig({
integrations: [
AutoImport({
imports: [
'./src/components/Toc.astro'
],
}),
mdx(),
],
});
これで mdx ファイルでコンポーネントを利用できるようになるので、あとは QA コンテンツ用のコンポーネントを実装していきます
QAコンテンツの表現方法
HTML Living Standard によると QA コンテンツを表現する際は dl / dt / dd タグを利用するのが良さそうです。そのため、HTML に変換した際に以下のような構成になるように実装していきます
<dl>
<dt> Question1
<dd> Answer1
<dt> Question2
<dd> Answer2
</dl>
QAコンテンツ用のコンポーネント
今回は QA コンテンツ用に2つのコンポーネントを実装しました
1つ目は QA の本文を囲う dl タグを表現するコンポーネントです
function FaqWrapper({ children }) {
return (
<dl className="faq">{children}</dl>
);
}
export default FaqWrapper;
2つ目は QA を表現するコンポーネントです。引数の q に質問文を渡して、引数の a に回答文を渡すことで QA を表示することができます
function Faq({ q, a }) {
return (
<>
<dt className="question">{q}</dt>
<dd className="answer">{a}</dd>
</>
);
}
export default Faq;
mdx では Faq コンポーネントの引数に QA を渡して、QA コンテンツ群を FaqWrapper コンポーネントで囲うことで利用することができます
<FaqWrapper>
<Faq
q="これは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数です"
a="これは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数です"
/>
<Faq
q="これは質問の時に使う引数です"
a="これは回答の時に使う引数です"
/>
</FaqWrapper>
あとは自分好みでスタイルを当てたら以下のように QA コンテンツを表現することができるようになります
- これは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数ですこれは質問の時に使う引数です
- これは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数ですこれは回答の時に使う引数です
- これは質問の時に使う引数です
- これは回答の時に使う引数です