prawaki
記事一覧へ戻る

技術メモ

MarkdownからNext.jsブログ記事を生成する仕組み

DBを使わず、リポジトリ内のMarkdownファイルからブログ記事を生成する、このサイトの記事パイプラインの実装。

  • Next.js
  • Markdown
  • TypeScript

このブログの記事は、DBではなくリポジトリ内のMarkdownファイル(content/posts/*.md)で管理しています。ビルド時にMarkdownを読み込んでHTMLに変換する仕組みを、lib/articles.tsという1ファイルにまとめました。この記事はその実装メモです。

frontmatterで記事のメタ情報を持つ

各記事ファイルの先頭に、YAML形式のfrontmatterでメタ情報を書きます。

---
title: "記事タイトル"
slug: "article-slug"
date: "2026-07-05"
description: "記事の概要"
tags: ["Next.js", "Markdown"]
category: "技術メモ"
published: true
---

パースにはgray-matterを使います。ポイントは、パース結果をそのまま信用せず、型を検証してから使うことです。titleが文字列でなければビルドを失敗させる、という方針にしておくと、frontmatterの書き間違いに公開前に気づけます。

function readString(value: unknown, fieldName: string) {
  if (typeof value !== "string" || value.length === 0) {
    throw new Error("Invalid article frontmatter: " + fieldName + " must be a string.");
  }
  return value;
}

Markdown本文をHTMLに変換する

本文の変換はremarkremark-htmlに任せます。

async function markdownToHtml(markdown: string) {
  const processedContent = await remark().use(html).process(markdown);
  return processedContent.toString();
}

ページ側は変換済みHTMLを受け取って表示するだけです。この処理はすべてビルド時に実行されるので、公開されるサイトは純粋な静的HTMLになります。

公開フラグと並び順

記事一覧はpublished: trueのものだけを日付の降順で返します。

return articles
  .filter((article) => article.published)
  .sort((a, b) => b.date.localeCompare(a.date));

published: falseにすれば下書きをリポジトリに置いたまま非公開にできます。日付はYYYY-MM-DDの文字列なので、文字列比較がそのまま日付順になります。

この設計にした理由

ページ側はgetAllArticles()getArticleBySlug()という2つの関数だけを使い、記事がMarkdown由来であることを知りません。将来、記事の置き場所をHeadless CMSやDBに変えたくなっても、この2つの関数の中身を差し替えるだけで済む設計にしています。

まずはこの小さな仕組みで運用を始めて、不便になったところから育てていきます。