コンテンツにスキップ

MarkdownとMDX

Markdownは、ブログ投稿やドキュメントのようなテキストを多用するコンテンツのオーサリングによく使います。Astroは標準的なMarkdownファイルをビルトインでサポートしており、タイトルや説明文、タグなどのカスタムメタデータを定義するためのフロントマターYAMLも使用できます。

@astrojs/mdxインテグレーションをインストールすると、MDX.mdx)ファイルにも対応し、MarkdownコンテンツでのJavaScript式やコンポーネントのサポートなどの追加機能を提供します。

Markdownコンテンツを書くには、どちらか一方、または両方のタイプのファイルを使用します。

Astroでは、MarkdownやMDXのファイルを専用のsrc/content/フォルダで管理できます。コンテンツコレクションは、コンテンツの整理、フロントマターの検証、コンテンツを扱う際のTypeScriptの自動型安全性確保に役立ちます。

  • ディレクトリsrc/content/
    • ディレクトリnewsletter/
      • week-1.md
      • week-2.md
    • ディレクトリauthors/
      • grace-hopper.md
      • alan-turing.md

詳しい使い方はAstroのコンテンツコレクションをご覧ください。

Astroは、/src/pages/ディレクトリ内の.md(または対応する別の拡張子)または.mdxファイルをページとして扱います。

このディレクトリ、またはサブディレクトリにファイルを置くと、ファイルのパス名を使って自動的にページルートを構築します。

src/pages/page-1.md
---
title: Hello, World
---
# こんにちは!
このMarkdownファイルは `your-domain.com/page-1/`にページを作成します。
スタイリングとしてはおそらく不十分ですが、Markdownは以下をサポートしています。
- **太字**_イタリック_
- リスト
- [リンク](https://astro.build)
- などなど

📚 Astroのファイルベースルーティングや、動的ルーティングを作成するオプションについてもっと読む。

Astroは、MarkdownとMDXファイルを使用する際に利用できる、追加のビルトインMarkdown機能を提供します。

Astroは、MarkdownおよびMDXページに、Astroレイアウトコンポーネントへの相対パス(またはエイリアス)を指定できる特別なフロントマター用のlayoutプロパティを提供します。

src/pages/posts/post-1.md
---
layout: ../../layouts/BlogPostLayout.astro
title: Astroの概要
author: Himanshu
description: Astroの凄さを知ってください!
---
これはMarkdownで書かれた記事です。

特定のプロパティは、Astro.propsを通してレイアウトコンポーネントで利用できます。たとえば、Astro.props.frontmatterを通して、フロントマターのプロパティにアクセスできます。

src/layouts/BlogPostLayout.astro
---
const {frontmatter} = Astro.props;
---
<html>
<!-- ... -->
<h1>{frontmatter.title}</h1>
<h2>投稿者: {frontmatter.author}</h2>
<p>{frontmatter.description}</p>
<slot /> <!-- Markdownコンテンツはここに挿入されます -->
<!-- ... -->
</html>

また、レイアウトコンポーネントでMarkdownをスタイリングすることもできます。

📚 Markdownのレイアウトの詳細を見る。

MarkdownとMDXで見出しを使用すると、自動的にアンカーリンクが作成され、ページ内の特定のセクションに直接リンクできます。

src/pages/page-1.md
---
title: コンテンツページ
---
## イントロダクション
Markdownで書くと、同じページの[結論](#結論)に内部リンクできます。
## 結論
URL`https://my-domain.com/page-1/#イントロダクション`を使って、ページ上のイントロダクションに直接移動できます。

特定の文字は、MarkdownおよびMDXにおいて、特別な意味を持っています。それらを表示したい場合、異なる構文を使用する必要があるかもしれません。表示するには、これらの文字の代わりにHTMLエンティティを使用します。

たとえば、< がHTML要素の先頭と解釈されないようにするには、&lt;と記述します。また、MDXで{がJavaScriptの式の先頭と解釈されないようにするには、&lcub;と記述します。

AstroのMDXインテグレーションを追加すると、JSX変数、式、コンポーネントによってMarkdownオーサリングを強化できます。

また、MDXにおけるMarkdownスタイルのフロントマターのサポートなど、標準的なMDXにさらに機能を追加しています。これにより、フロントマター layoutプロパティなど、Astroの組み込みMarkdown機能のほとんどを使用できます。

.mdxファイルは、AstroのHTMLライクな構文ではなく、MDX構文で記述する必要があります。

MDXは、export文を使用してMDXコンテンツに変数を追加することをサポートします。これらの変数は、テンプレート自体でも、ファイルをどこかにインポートするときに名前付きプロパティとしてでもアクセスできます。

たとえば、MDXページやコンポーネントからtitleフィールドをエクスポートして、{JSX expressions}で見出しとして使用できます。

/src/pages/posts/post-1.mdx
export const title = 'はじめてのMDXの投稿'
# {title}

Astro MDXインテグレーションには、MDXでフロントマターを使用するためのサポートがデフォルトで含まれています。Markdownファイルと同じようにフロントマタープロパティを追加すると、これらの変数はテンプレートやlayoutコンポーネント内において、あるいはどこかにファイルをインポートするときに名前付きプロパティとして使用する際にアクセスできます。

/src/pages/posts/post-1.mdx
---
layout: '../../layouts/BlogPostLayout.astro'
title: 'はじめてのMDXの投稿'
---
# {frontmatter.title}

MDXインテグレーションをインストールすると、AstroコンポーネントUIフレームワークコンポーネントの両方をMDX(.mdx)ファイルにインポートして、他のAstroコンポーネントと同じように使用できるようになります。

必要であれば、UIフレームワークのコンポーネントにclient:directiveをつけ忘れないよう注意してください。

MDX docsでimportとexport文の他の使用例を確認してください。

src/pages/about.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 />

カスタムコンポーネントをHTML要素に割り当てる

セクションタイトル: カスタムコンポーネントをHTML要素に割り当てる

MDXを使用すると、標準のHTML要素の代わりに、Markdown構文をカスタムコンポーネントにマッピングできます。これにより、標準的なMarkdown構文で記述しながら、選択された要素に特別なコンポーネントスタイルを適用できます。

カスタムコンポーネントを.mdxファイルにインポートし、標準のHTML要素をカスタムコンポーネントにマップするcomponentsオブジェクトをエクスポートします。

src/pages/about.mdx
import Blockquote from '../components/Blockquote.astro';
export const components = {blockquote: Blockquote}
> この引用文はカスタムBlockquoteになります。
src/components/Blockquote.astro
---
const props = Astro.props;
---
<blockquote {...props} class="bg-blue-50 p-4">
<span class="text-4xl text-blue-600 mb-2"></span>
<slot /> <!-- 子コンテンツのために `<slot/>` を必ず追加してください! -->
</blockquote>

カスタムコンポーネントとして上書き可能なHTML要素の全リストは、MDXのウェブサイトをご覧ください。

MarkdownファイルやMDXファイルをAstroファイルに直接インポートできます。これにより、そのMarkdownコンテンツや、AstroのJSXライクな式で使用できるフロントマターの値などのプロパティにアクセスできます。

import文で特定の1ページを、Astro.glob()で複数のページをインポートできます。

src/pages/index.astro
---
// 1つのファイルのインポート
import * as myPost from '../pages/post/my-post.md';
// Astro.globによる複数ファイルのインポート
const posts = await Astro.glob('../pages/post/*.md');
---

AstroコンポーネントでMarkdownやMDXファイルをインポートすると、そのエクスポートされたプロパティを含むオブジェクトが取得されます。

/src/pages/posts/great-post.md
---
title: '史上最高の投稿'
author: 'Ben'
---
**素晴らしい**投稿!
src/pages/my-posts.astro
---
import * as greatPost from '../pages/post/great-post.md';
const posts = await Astro.glob('../pages/post/*.md');
---
<p>{greatPost.frontmatter.title}</p>
<p>執筆者: {greatPost.frontmatter.author}</p>
<p>投稿アーカイブ:</p>
<ul>
{posts.map(post => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
</ul>

MDXファイルでは、フロントマターとexport文の両方からプロパティにアクセスできます。

/src/pages/posts/mdx-post.mdx
---
title: '史上最高の投稿'
author: 'Ben'
---
export const description = '快適に過ごそう! これは素晴らしい読み物になりそうです。'
**素晴らしい**投稿!
src/pages/my-posts.astro
---
import * as greatPost from '../pages/post/mdx-post.mdx';
---
<p>{greatPost.frontmatter.title}</p>
<p>執筆者: {greatPost.frontmatter.author}</p>
<p>{greatPost.description}</p>

オプションとして、TypeScriptのジェネリックを使用してfrontmatter変数の型を指定できます。

src/pages/index.astro
---
interface Frontmatter {
title: string;
description?: string;
}
const posts = await Astro.glob<Frontmatter>('../pages/post/*.md');
---
<ul>
{posts.map(post => <li>{post.frontmatter.title}</li>)}
<!-- post.frontmatter.title は `string`! -->
</ul>

.astroコンポーネントでimport文またはAstro.glob()を使用した場合、以下のプロパティが使用できます。

  • file - ファイルの絶対パス(例:/home/user/projects/.../file.md)。
  • url - もしページなら、そのページのURL (例:/en/guides/markdown-content)。
  • frontmatter - ファイルのYAML フロントマターで指定された全データを含みます。
  • getHeadings - ファイル内のすべての見出し(すなわちh1 -> h6要素)の配列を返す非同期関数です。各見出しのslugは、与えられた見出しに対して生成されたIDに対応し、アンカーリンクに使用できます。このリストは次の型に従います。{ depth: number; slug: string; text: string }[]
  • Content - ファイルのレンダリングされた完全なコンテンツを返すコンポーネントです。
  • (Markdownのみ) rawContent() - 生のMarkdownドキュメントを文字列として返す関数です。
  • (Markdownのみ) compiledContent() - HTML文字列にコンパイルされたMarkdownドキュメントを返す関数です。これはフロントマターで設定されたレイアウトを含まないことに注意してください。Markdownドキュメント自身のみがHTMLとして返されます。
  • (MDXのみ) - MDXファイルは、export文を使用してデータをエクスポートすることもできます。

Contentをインポートすると、MarkdownまたはMDXファイルを完全にレンダリングしたコンテンツを返すコンポーネントを使用できます。

src/pages/content.astro
---
import {Content as PromoBanner} from '../components/promoBanner.md';
---
<h2>本日のプロモーション</h2>
<PromoBanner />

Markdown/MDXファイルをsrc/pages/ディレクトリに置いてページルートを作成する代わりに、ページを動的に生成できます

Markdownコンテンツにアクセスするには、Astroページのprops<Content/>コンポーネントを渡します。そして、Astro.propsからコンポーネントを取得し、ページテンプレートにレンダリングできます。

src/pages/[slug].astro
---
export async function getStaticPaths() {
const posts = await Astro.glob('../posts/**/*.md')
return posts.map(post => ({
params: {
slug: post.frontmatter.slug
},
props: {
post
},
}))
}
const { Content } = Astro.props.post
---
<article>
<Content/>
</article>

MDXファイルでは、export文を使用してデータをエクスポートできます。

たとえば、MDXページやコンポーネントからtitleフィールドをエクスポートできます。

/src/pages/posts/post-1.mdx
export const title = 'はじめてのMDXの投稿'

このtitleimportAstro.glob()文からアクセスできます。

src/pages/index.astro
---
const posts = await Astro.glob('./*.mdx');
---
{posts.map(post => <p>{post.title}</p>)}

インポートしたMDXを使ったカスタムコンポーネント

セクションタイトル: インポートしたMDXを使ったカスタムコンポーネント

インポートしたMDXコンテンツをレンダリングする際、componentsプロパティでカスタムコンポーネントを渡せます。

src/pages/page.astro
---
import { Content, components } from '../content.mdx';
import Heading from '../Heading.astro';
---
<!-- # 記法のためのカスタムの <h1> を作成し、`content.mdx`で定義されたカスタムコンポーネントを適用します。 -->
<Content components={{...components, h1: Heading }} />

AstroのMarkdownサポートは、活発なエコシステムを持つ強力なパースおよび処理ツールであるremarkによって提供されています。Pandocやmarkdown-itなどの他のMarkdownパーサーは、現在サポートされていません。

AstroはデフォルトでGitHub-flavored Markdownプラグインを適用しています。これにより、テキストからクリック可能なリンクが生成されるなど、いくつかの利点があります。

remarkがMarkdownをどのように解析するかは、astro.config.mjsでカスタマイズできます。Markdownの設定オプションの全リストをご覧ください。

Astroは、MarkdownおよびMDXのためのサードパーティのremarkおよびrehypeプラグインの追加をサポートしています。これらのプラグインにより、目次の自動生成アクセス可能な絵文字ラベルの適用など、新しい機能でMarkdownを拡張できます。

人気のあるプラグインはawesome-remarkawesome-rehypeを参照するのがおすすめです。具体的なインストール方法については、各プラグインのREADMEをご覧ください。

この例では、Astroのデフォルトプラグインを維持したまま、MarkdownとMDXにremark-tocを適用し、MDXのみにrehype-accessible-emojisを適用しています。

astro.config.mjs
import { defineConfig } from 'astro/config';
import remarkToc from 'remark-toc';
import { rehypeAccessibleEmojis } from 'rehype-accessible-emojis';
export default {
markdown: {
// .md と .mdx ファイルに適用
remarkPlugins: [remarkToc],
rehypePlugins: [rehypeAccessibleEmojis],
},
}

なお、デフォルトでは、remarkTocは目次を表示するために、ページ上に「ToC」または「Table of Contents」の見出し(大文字小文字を区別しない)を必要とします。

Astroは、MarkdownやMDXファイルのすべての見出し要素(<h1><h6>)にid属性を注入し、MarkdownでエクスポートしたプロパティでこれらのIDを取得するためのgetHeadings()ユーティリティを提供しています。

id属性(rehype-slugなど)を注入するrehypeプラグインを追加することで、これらの見出しIDをカスタマイズできます。Astroのデフォルトではなく、あなたのカスタムIDがHTML出力とgetHeadings()が返すアイテムに反映されます。

デフォルトでは、Astroはrehypeプラグインが実行された後にid属性を注入します。もし、カスタムrehypeプラグインの1つがAstroによって注入されたIDにアクセスする必要がある場合、AstroのrehypeHeadingIdsプラグインを直接インポートして使用できます。rehypeHeadingIdsに依存するプラグインの前に必ずrehypeHeadingIdsを追加してください。

astro.config.mjs
import { defineConfig } from 'astro/config';
import { rehypeHeadingIds } from '@astrojs/markdown-remark';
import { otherPluginThatReliesOnHeadingIDs } from 'some/plugin/source';
export default defineConfig({
markdown: {
rehypePlugins: [
rehypeHeadingIds,
otherPluginThatReliesOnHeadingIDs,
],
},
});

プラグインを設定するためには、ネストされた配列の中で、プラグインの後にオプションオブジェクトを指定します。

以下の例では、remarkTocプラグインにheadingオプションを追加して目次の配置を変更し、rehype-autolink-headingsプラグインにbehaviorオプションを追加して、見出しテキストの後にアンカータグを追加しています。

astro.config.mjs
import remarkToc from 'remark-toc';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
export default {
markdown: {
remarkPlugins: [ [remarkToc, { heading: "contents"} ] ],
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'append' }]],
},
}

remarkやrehypeのプラグインを使用することで、すべてのMarkdownファイルやMDXファイルにfrontmatterプロパティを追加できます。

  1. プラグインのfile引数のdata.astro.frontmatterプロパティにcustomPropertyを追加してください。

    example-remark-plugin.mjs
    export function exampleRemarkPlugin() {
    // すべての remark および rehype プラグインは、別の関数を返します。
    return function (tree, file) {
    file.data.astro.frontmatter.customProperty = '生成されたプロパティ';
    }
    }
  2. このプラグインをmarkdownまたはmdxのインテグレーション設定に適用します。

    astro.config.mjs
    import { exampleRemarkPlugin } from './example-remark-plugin.mjs';
    export default {
    markdown: {
    remarkPlugins: [exampleRemarkPlugin]
    },
    };

    または

    astro.config.mjs
    import { defineConfig } from 'astro/config';
    import { exampleRemarkPlugin } from './example-remark-plugin.mjs';
    export default defineConfig({
    integrations: [
    mdx({
    remarkPlugins: [exampleRemarkPlugin],
    }),
    ],
    });

これで、すべてのMarkdownまたはMDXファイルは、フロントマターにcustomPropertyを持ち、Markdownのインポート時やレイアウトのAstro.props.frontmatterプロパティから利用できるようになります。

関連レシピ: Add reading time (EN)

AstroのMDX統合は、デフォルトでプロジェクトの既存のMarkdown設定を継承します。個々のオプションを上書きするには、MDXの構成で同等のものを指定します。

次の例では、GitHub-Flavored Markdownを無効にし、MDXファイルに対して異なるremarkプラグインのセットを適用しています。

astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
export default defineConfig({
markdown: {
syntaxHighlight: 'prism',
remarkPlugins: [remarkPlugin1],
gfm: true,
},
integrations: [
mdx({
// Markdownから継承した`syntaxHighlight`
// Markdownの`remarkPlugins`は無視され、
// `remarkPlugin2`のみが適用される
remarkPlugins: [remarkPlugin2],
// `gfm`は`false`に上書きされる
gfm: false,
})
]
});

MDXからMarkdownの設定を継承しないようにするには、extendMarkdownConfigオプション)(デフォルトで有効)をfalseに設定します。

astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
export default defineConfig({
markdown: {
remarkPlugins: [remarkPlugin],
},
integrations: [
mdx({
// Markdownの設定は無視される
extendMarkdownConfig: false,
// `remarkPlugins`は適用されない
})
]
});

Astroは、ShikiPrismをビルトインでサポートしています。これは、次のようなシンタックスハイライトを提供します。

Shikiはデフォルトで有効になっており、github-darkテーマであらかじめ設定されています。コンパイルされた出力は、余計なCSSクラス、スタイルシート、クライアントサイドJSのないインラインstyleに限定されます。

Shikiはデフォルトのシンタックスハイライトです。すべてのオプションはshikiConfigオブジェクト経由で以下のように設定できます。

astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
markdown: {
shikiConfig: {
// Shikiの組み込みテーマから選択(または独自のテーマを追加)
// https://github.com/shikijs/shiki/blob/main/docs/themes.md
theme: 'dracula',
// カスタム言語の追加
// 注:Shikiには.astroを含む無数の言語が内蔵されています。
// https://github.com/shikijs/shiki/blob/main/docs/languages.md
langs: [],
// 水平スクロールを防ぐために文字の折り返しを有効にする
wrap: true,
},
},
});

Shikiの定義済みテーマを使用する代わりに、ローカルファイルからカスタムテーマをインポートできます。

astro.config.mjs
import { defineConfig } from 'astro/config';
import customTheme from './my-shiki-theme.json';
export default defineConfig({
markdown: {
shikiConfig: { theme: customTheme },
},
});

また、テーマ、ライトモードとダークモードの切り替え、CSS変数によるスタイリングについて詳しく調べるには、Shiki公式のテーマドキュメントを読むことをおすすめします。

デフォルトのシンタックスハイライトモードを変更する

セクションタイトル: デフォルトのシンタックスハイライトモードを変更する

デフォルトを'prism'に切り替えたい場合、またはシンタックスハイライトを完全に無効にしたい場合は、markdown.syntaxHighlighting設定オブジェクトを使用できます。

astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
markdown: {
// 'shiki'(デフォルト)、'prism'、またはハイライトを無効にする場合は false を指定します
syntaxHighlight: 'prism',
},
});

Prismの使用を選択した場合、AstroはPrismのCSSクラスを代わりに適用します。なお、シンタックスハイライトを表示させるには、独自のCSSスタイルシートを用意する必要があります

  1. 利用可能なPrismテーマから、あらかじめ用意されたスタイルシートを選択します。
  2. このスタイルシートをプロジェクトのpublic/ディレクトリに追加します。
  3. これをレイアウトコンポーネント<head><link>タグで読み込みます。(Prismの基本的な使い方を参照してください)

また、オプションや使い方については、Prismの対応言語一覧をご覧ください。

Astroは主にプロジェクトディレクトリ内に保存されるローカルのMarkdownファイル用に設計されています。しかし、リモートソースからMarkdownを取得する必要がある特定のケースもあるかもしれません。たとえば、ウェブサイトを構築するとき(またはSSRを使用している場合、ユーザーがウェブサイトにリクエストを行うとき)、リモートAPIからMarkdownを取得してレンダリングする必要があるかもしれません。

Astroは、標準でリモートのMarkdownをサポートしていません! リモートのMarkdownを取得し、それをHTMLにレンダリングするには、npmから独自のMarkdownパーサーをインストールし、設定する必要があります。これは、カスタマイズしたAstroのビルトインMarkdownとMDXの設定をいずれからも継承しません。プロジェクトでこれを実装する前に、これらの制限を理解しておいてください。

src/pages/remote-example.astro
---
// 例: Markdown をリモートAPIから取得し、
// それを実行時にHTMLにレンダリングする。
// "marked" (https://github.com/markedjs/marked) を利用
import { marked } from 'marked';
const response = await fetch('https://raw.githubusercontent.com/wiki/adam-p/markdown-here/Markdown-Cheatsheet.md');
const markdown = await response.text();
const content = marked.parse(markdown);
---
<article set:html={content} />