注目イベント!
アドベントカレンダー2024開催中!
一年を締めくくる特別なイベント、アドベントカレンダーを今年も開催しています!
初心者からベテランまで楽しめる内容で、毎日新しい技術トピックをお届けします。
詳細はこちらから!
event banner

Astro 2.0 + MDX + Recharts で Markdown ページにインタラクティブなチャートを描画する

| 7 min read
Author: masahiro-kondo masahiro-kondoの画像

人気の静的サイトジェネレーター Astro 2.0 がリリースされました。

Astro 2.0 | Astro

Content Collections API、Hybrid Rendering、Hot Module Reloading などの新機能が追加されました。

Information

Astro の概要については、昨年9月に1.0がリリースされた時記事にしましたので、参考にしてください。

コンテンツ重視の静的サイトジェネレーター Astro でドキュメントサイトを構築する

Content Collections API は Markdown/MDX の記述ミスでビルドが失敗するのを防ぎ、開発者体験を向上させる機能です。

ページを "blog"、"newsletter"、"product" などのコレクションに整理すると Astro が次のような処理をしてくれます。

  • スキーマ検証
  • SEO ベストプラクティス
  • 有益なエラーメッセージの出力
  • 型の自動生成
  • インライン型エラー、オートコンプリート など

ページのコンポーネントを astro 形式のファイルで書く時にオートコンプリートが効くようになっています。エラーメッセージの改善については、例えば Blog ポストのヘッダーに 'description' タグが抜けていた場合、下のようなエラーメッセージが出てエラー原因が分かりやすく表示されています。

Content entry frontmatter error message

Information

Blog などのコレクションは TypeScript の型として定義されており、この型を使用して frontmatter のバリデーションが行われます。

const blog = defineCollection({
  // Type-check frontmatter using a schema
  schema: z.object({
    title: z.string(),
    description: z.string(),
    // Transform string to Date object
    pubDate: z
      .string()
      .or(z.date())
      .transform((val) => new Date(val)),
    updatedDate: z
      .string()
      .optional()
      .transform((str) => (str ? new Date(str) : undefined)),
    heroImage: z.string().optional(),
  }),
});

プロジェクト作成

#

今回は MDX で生成した静的ページへのインタラクティブなチャートコンポーネントのハイドレートを試しました。ブログのテンプレートでプロジェクトを作成しました。

npm create astro@latest -- --template blog

Create project

生成されたプロジェクトのディレクトリ構成は以下のようになっています。

.
├── README.md
├── astro.config.mjs
├── package-lock.json
├── package.json
├── public
├── src
│   ├── components
│   │   ├── BaseHead.astro
│   │   ├── Footer.astro
│   │   ├── Header.astro
│   │   └── HeaderLink.astro
│   ├── consts.ts
│   ├── content
│   │   ├── blog
│   │   │   ├── first-post.md
│   │   │   ├── markdown-style-guide.md
│   │   │   ├── second-post.md
│   │   │   ├── third-post.md
│   │   │   └── using-mdx.mdx
│   │   └── config.ts
│   ├── env.d.ts
│   ├── layouts
│   │   └── BlogPost.astro
│   ├── pages
│   │   ├── about.md
│   │   ├── blog
│   │   │   ├── [...slug].astro
│   │   │   └── index.astro
│   │   ├── index.astro
│   │   └── rss.xml.js
│   └── styles
│       └── global.css
└── tsconfig.json

作成したプロジェクトは以下のリポジトリに置きました。

GitHub - kondoumh/astro-blog-example

Netlify にデプロイしたサイトは以下です。

https://kondoumh-astro-blog-example.netlify.app/

MDX を書く

#

MDX は JSX を Markdown に書けるようにする拡張です。MDX は触ったことがなかったので、公式サイトを参照して概要を把握しました。

Markdown for the component era | MDX

MDX 自体は Astro 1.0 の時からサポートされていました。上記で作成したブログのプロジェクトにはあらかじめ組み込まれています。MDX を既存の Astro プロジェクトで使えるようにするには以下のコマンドを実行します。

npx astro add mdx

VS Code で MDX ファイルを扱う場合、files.associations で拡張子の設定を入れると Markdown 部分はコードハイライトが効くようになります。

"files.associations": {
    "*.mdx": "markdown"
},

MDX に書く JSX のコードもハイライトしたい場合は、vscode-mdx 拡張を入れるとよいでしょう。

MDX - Visual Studio Marketplace

React インテグレーションと Recharts のインストール

#

今回は、React 用のチャートライブラリ Recharts を使用するため、まず React インテグレーションを追加しました。

npm i -D  @astrojs/react

ブログのテンプレートから生成している場合は astro.config.mjs に react の import と integration を追加します。

  • astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import react from '@astrojs/react'; // 追加

import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://example.com',
  integrations: [mdx(), sitemap(), react()], // react() を追加
});

Recharts をインストールします。

npm i recharts

以上で準備は完了です。

Chart コンポーネントの作成

#

まず、Recharts でラインチャートを描く React コンポーネントを src/content/blog 配下に追加します[1]。このコンポーネントの data と LineChart のプロパティは Recharts の SimpleLineChart example と同じです。

  • src/content/blog/example-chart.jsx
import React from 'react'

import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
} from 'recharts'

const data = [
  {
    name: 'Page A',
    uv: 4000,
    pv: 2400,
    amt: 2400,
  },
  {
    name: 'Page B',
    uv: 3000,
    pv: 1398,
    amt: 2210,
  },
  {
    name: 'Page C',
    uv: 2000,
    pv: 9800,
    amt: 2290,
  },
  {
    name: 'Page D',
    uv: 2780,
    pv: 3908,
    amt: 2000,
  },
  {
    name: 'Page E',
    uv: 1890,
    pv: 4800,
    amt: 2181,
  },
  {
    name: 'Page F',
    uv: 2390,
    pv: 3800,
    amt: 2500,
  },
  {
    name: 'Page G',
    uv: 3490,
    pv: 4300,
    amt: 2100,
  },
]

export default () => (
  <div>
    <LineChart
      width={500}
      height={300}
      data={data}
      margin={{
        top: 10,
        right: 30,
        left: 20,
        bottom: 5,
      }}
    >
      <CartesianGrid strokeDasharray="3 3" />
      <XAxis dataKey="name" />
      <YAxis />
      <Tooltip />
      <Legend />
      <Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{ r: 8 }} />
      <Line type="monotone" dataKey="uv" stroke="#82ca9d" />
    </LineChart>
  </div>
)

このコンポーネントを MDX ファイルに直接書いても描画はされますが、それはサーバーサイドで SVG をレンダリングするだけで、マウスオーバー時にポイントデータをツールチップで表示するなどのインタラクティブ性はありません。インタラクティブ性を持たせるために、Astro のコンポーネントを追加しました。

  • src/content/blog/chart.astro
---
import ExampleChart from './example-chart.jsx';
---

<html lang="en">
  <body>
    <ExampleChart client:load></ExampleChart>
  </body>
</html>

ExampleChart タグの client:load ディレクティブにより、ページロード時の JavaScript ハイドレーションが実行され、クライアント側のイベントハンドラーが有効化されます。

MDX へのチャート埋め込みのコードです。chart.astro を埋め込んでハイドレーションを有効にしたものと、example-chart.jsx を直接埋め込んだもの(SSG)を上下に並べました。

  • src/content/blog/mdx-sandbox.mdx
import Chart from './chart.astro'

## Example Chart (hydrated)

<Chart />

import ExampleChart from './example-chart.jsx'

## Example Chart (SSG)

<ExampleChart />

レンダリング結果です。chart.astro でハイドレーションしたチャート(上)は、マウスでポイントした箇所のデータが判例として表示されますが、SSG の方はそのような動きはしません。

実際の画面は以下から閲覧可能です。

MDX Sandbox

最後に

#

Astro 2.0 と MDX と Recharts で Markdown ファイルにインタラクティブなチャートを埋め込んでみました。MDX により Markdown の文書が拡張され、インタラクティブ性やコンポーネント化が実現されるのはなかなかすごいと思います。

MDX については Astro 1.0 でも実現できた機能ではありますが、Content Collections などの機能追加により開発体験が向上しています。Vite 4.0 にアップデートされ、ビルドも速く待ちがほとんどなく快適です。設定もほとんど不要で使い始められるのも楽です。

筆者は Hugo で個人ページを作成していますが、そろそろ Astro にスイッチしようかなと思わされました。


  1. コンポーネントの配置先ディレクトリは任意です。 ↩︎

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。