gray-matter で Markdown の frontmatter をパース — TypeScript で型安全に使う
gray-matter は Markdown ファイルの先頭に書く YAML ヘッダー(frontmatter)を JavaScript オブジェクトとして取り出すライブラリです。Next.js の静的ブログを作る際に必ずと言っていいほど登場するので、基本的な使い方から型安全な活用法まで整理します。
frontmatter とは
Markdown ファイルの先頭に --- で囲んで書く YAML 形式のメタデータ領域を「frontmatter」と呼びます。
---
title: "記事タイトル"
date: "2026-02-24"
tags: ["Next.js", "TypeScript"]
---
ここから本文が始まる。
--- より前の部分が frontmatter、それ以降が Markdown 本文です。gray-matter はこの 2 つを分離して返します。
インストール
npm install gray-matter
TypeScript で使う場合、型定義は本体に同梱されているので @types/gray-matter は不要です。
基本的な使い方
import fs from "fs";
import matter from "gray-matter";
const fileContents = fs.readFileSync("posts/hello.md", "utf-8");
const { data, content } = matter(fileContents);
console.log(data); // frontmatter オブジェクト
console.log(content); // Markdown 本文(frontmatter を除いた部分)
matter() の戻り値には主に以下のプロパティが含まれます。
| プロパティ | 内容 |
|---|---|
data | frontmatter をパースしたオブジェクト |
content | frontmatter を除いた Markdown 本文 |
orig | 元のファイル内容(Buffer) |
TypeScript で型安全に使う
data の型はデフォルトで { [key: string]: any } になります。TypeScript で型安全に扱うには、frontmatter の形を型として定義してキャストします。
import fs from "fs";
import matter from "gray-matter";
type PostFrontmatter = {
title: string;
description?: string;
date: string;
image: string | null;
tags: string[] | null;
};
const fileContents = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(fileContents) as matter.GrayMatterFile<string> & {
data: PostFrontmatter;
};
// data.title は string として型推論される
// data.description は string | undefined
matter.GrayMatterFile<string> に data のみ上書きするインターセクション型を使うのがポイントです。matter() の戻り値全体を捨てずに済みます。
記事一覧取得での実装例
// src/app/lib/functions.ts(抜粋)
import fs from "fs";
import path from "path";
import matter from "gray-matter";
type PostFrontmatter = {
title: string;
description?: string;
date: string;
image: string | null;
tags: string[] | null;
};
const getPostData = async (): Promise<PostItem[]> => {
const postsDirectory = path.join(process.cwd(), "posts");
const filenames = fs.readdirSync(postsDirectory);
const posts = filenames
.map((filename) => {
const filePath = path.join(postsDirectory, filename);
const fileContents = fs.readFileSync(filePath, "utf-8");
const { data } = matter(fileContents) as matter.GrayMatterFile<string> & {
data: PostFrontmatter;
};
return {
slug: filename.replace(/\.md$/, ""),
title: data.title,
description: data.description,
date: data.date,
image: data.image,
tags: data.tags || [],
contentHtml: "",
};
})
.sort((postA, postB) =>
new Date(postA.date) > new Date(postB.date) ? -1 : 1
);
return posts;
};
記事一覧取得では content(本文)は不要なので分割代入で data だけ取り出しています。
記事詳細ページで本文も使う場合
// src/app/posts/[slug]/page.tsx(抜粋)
import matter from "gray-matter";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
async function createPostData(slug: string) {
const filePath = path.join(process.cwd(), "posts", `${slug}.md`);
const fileContents = fs.readFileSync(filePath, "utf8");
const { data, content } = matter(fileContents);
// content を remark → rehype → HTML に変換
const processedContent = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(content);
return {
title: data.title,
date: data.date,
contentHtml: processedContent.toString(),
};
}
matter() で本文と frontmatter を分離してから、content だけを Markdown パイプラインに流すのが基本的な流れです。
frontmatter の設計指針
必須フィールドは最小限に
title と date だけを必須とし、あとは省略可能にすることで記事作成のハードルを下げられます。
---
title: "記事タイトル" # 必須
date: "2026-02-24" # 必須
---
description は省略可能にして自動補完
const description: string = data.description ?? generateExcerpt(content);
null 許容フィールドにはデフォルト値を
tags が null のままだと .map() でエラーになるので、読み込み時に空配列に変換します。
tags: data.tags || [],
ハマりやすいポイント
日付はダブルクォートで囲む
date: "2026-02-24" # 文字列として扱われる(推奨)
date: 2026-02-24 # Date オブジェクトになることがある
date: 2026-02-24 と書くと YAML パーサーが Date オブジェクトとして解釈してしまう場合があります。文字列として扱いたいなら必ずクォートします。
content には frontmatter が含まれない
matter() が返す content には frontmatter 部分(--- で囲まれた領域)は含まれません。独自に --- を除去する処理を書く必要はありません。
YAML の配列記法
# インライン記法
tags: ["Next.js", "TypeScript"]
# ブロック記法(どちらでも正しくパース)
tags:
- Next.js
- TypeScript
関連ツールとの比較
| ライブラリ | 特徴 | 向いているケース |
|---|---|---|
gray-matter | YAML/JSON/TOML/CoffeeScript 対応。型定義内蔵 | Next.js・Gatsby・Astro 問わず使いたいとき |
front-matter | YAML 専用、非常にシンプル | 小規模スクリプトや YAML しか使わない場合 |
remark-frontmatter | remark パイプラインに統合できる | unified パイプラインの中で frontmatter を処理したいとき |
Next.js や Astro の静的ブログなら gray-matter が事実上の標準で、エコシステムのサンプルコードも最も多い選択肢です。
まとめ
gray-matterはmatter(fileContents)の 1 行で frontmatter と本文を分離できる- TypeScript では
matter.GrayMatterFile<string> & { data: YourType }でキャストして型安全にする descriptionの自動補完やtags || []のデフォルト値など、読み込み時に欠損を吸収しておくのが実装を楽にするコツ- 日付フィールドは必ずクォートして文字列として扱う