Astroで作っている記事にコンポーネントを使いまわしたい
背景・課題
巨人の肩にたてたなら、巨人に丁寧にお礼したい
最近記事を書く中で、参考にさせていただいた記事のURLを挿入することが増えてきました。
ただ私の記事はAstroをベースにしたMarkdownファイルで作成されているので、どうしてもリンクカードの表現が↓みたいにしょぼくなってしまうんですね
[参考URL](https://zaap-dev.netlify.app/)
これじゃあ感謝が伝わんない!
もっとリッチなリンクカードコンポーネントを作ってmdファイルに埋め込みたい..!
汎用的に考えるなら、Astroのコンポーネントをmdファイルに埋め込みたい..!
ぶっちゃけ他の記事で使わないようなコンポーネントなら、mdファイル内で html要素で構築できます。 が!今回のように他の記事でも使い回すことを考えるとイけてない。
ということで前置きが長くなりましたが↓のユーザーストーリーに応えられる方法を共有したいと思います。
Astroをベースとした記事を書いているユーザーとして、複数の記事(Markdownファイル)でコンポーネントを使いまわしたい。なぜなら毎回Markdownファイルでhtml要素でコンポーネントを作るのは手間だから。
感謝を伝えるリンクカードを毎回書くのを手間だと考えている時点で自分の浅さが出ている気がしますがまあいいでしょう
解決策
mdxを使おうぜ!
mdxはJSXが利用できるMarkdownです。本記事ではJSXに詳しく触れませんが、Reactとかでコンポーネントを作るときのあれです
Astroにはインテグレーションという仕組みがあり、ReactなどのUIフレームワークやMDXといった拡張機能をサイト内で扱えるので、これを活かしてみたいと思います。
1. サイト内でmdxを利用できるようにパッケージをインポート
npx astro add mdx
2. astro.config.mjsでmdxを利用するように設定を修正
export default defineConfig({
site: "https://bzine.netlify.app/",
integrations: [mdx()], // ← この行を追加(元々integrationsがある場合はmdx()を要素に追加するだけでOK)
3. コンポーネントを作成する
↓はこのサイトで新しく利用するようになったリンクカードコンポーネントの例です
リンクカードコンポーネントのコード例
---
interface Props {
url: string;
title?: string;
}
const { url, title: titleProp } = Astro.props;
// "DOCS · BLENDER.ORG" 形式に整形(サブドメインがあれば分離)
const hostname = new URL(url).hostname.replace(/^www\./, "");
const parts = hostname.split(".");
const subdomain = parts.length > 2 ? parts[0].toUpperCase() : null;
const mainDomain = (parts.length > 2 ? parts.slice(1) : parts)
.join(".")
.toUpperCase();
const domainLabel = subdomain ? `${subdomain} · ${mainDomain}` : mainDomain;
let title = titleProp;
if (!title) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
const html = await res.text();
const match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
title = match ? match[1].trim() : url;
} catch {
title = hostname;
}
}
---
<div class="link-card-wrap">
<div class="thanks-badge">参考にさせていただきます 🙏</div>
<span class="spark s1" aria-hidden="true">✦</span>
<span class="spark s2" aria-hidden="true">⋆</span>
<span class="spark s3" aria-hidden="true">✦</span>
<span class="spark s4" aria-hidden="true">⋆</span>
<span class="spark s5" aria-hidden="true">✦</span>
<a href={url} target="_blank" rel="noopener noreferrer" class="link-card">
<div class="link-card-body">
<div class="link-card-domain">{domainLabel}</div>
<div class="link-card-title">{title}</div>
</div>
<svg
class="link-card-icon"
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
aria-hidden="true"
>
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</a>
</div>
<style>
/* ...省略... */
</style>
4. 作成したコンポーネントを.mdxファイルでインポートして表示させる
---
メタデータ
---
import LinkCard from 'components/LinkCard.astro'
記
事
本
文
...は以下記事を参考。
<LinkCard url="https://zaap-dev.netlify.app/">
今回リンクカードにキラキラと感謝バッジを出すようにしてみました。
cssとか何もわからんなので、今回リンクカードのデザインに関するブレインストーミングや設計・実装はclaude codeのプラグイン「superpowers」を使ってやってみました。
ブレインストーミングの段階で、向こう側からデザイン案を複数ブラウザ上で表示してくれて、視覚的にコンポーネントのデザインがサクサク決められてすげぇえなと思いました。

まとめ
Astroのブログ記事でコンポーネントを使い回したいなら、mdxを試してみよう!
.md → .mdx に拡張子を変えてインテグレーションを追加するだけで、コンポーネントを使いまわせる!
今回参考にさせていただいた本家公式ドキュメントはこちら