肥大化したデータベースに別れを告げ、純粋な執筆体験へ

知識有料プラットフォームを構築する際、最も頭を悩ませる問題は通常「記事をどこに保存するか?どうレイアウトするか?」です。 従来の方法は、WYSIWYGエディタ(CKEditorやTinyMCEなど)を搭載したCMS(コンテンツ管理システム)を構築し、ユーザーが作成した大量のHTMLタグを含む「汚れた文字列」をリレーショナルデータベースに詰め込むことです。 この方法には3つの致命的な欠点があります:

  1. レイアウトが崩れやすい:特にコードブロックを貼り付けると、しばしばぐちゃぐちゃになります。
  2. データベースが肥大化する:数十万字のテキストと画像コンテンツがデータベースの検索速度を著しく低下させ、移行が困難になります。
  3. バージョン管理機能がない:記事の過去の変更を追跡するのが難しく、誤って削除すると復元できません。

これが、Vibe Tutorで従来のCMSを完全に捨て、エンジニアが最も愛するMarkdownファイルシステムを採用した理由です。


Markdownとは?

Markdownは軽量マークアップ言語で、GitHubのREADME.mdを書く際に毎日使用しています。 いくつかの簡単な記号(#は見出し、**は太字、```はコードブロックを表す)を使うだけで、構造が明確で整理されたドキュメントを作成できます。

さらに素晴らしいのは、Markdownファイルがプレーンテキストファイルであることです。これにより:

  • ほとんどストレージを消費しません
  • Gitを使ったバージョン管理(Version Control)が完璧に機能します
  • 好みのIDE(VS CodeやCursorなど)を使ってコースを執筆でき、強力な補完とショートカットを享受できます

Vibe Tutorの動的レンダリングマジック

では、サーバーのフォルダに置かれた.mdファイルを、ユーザー画面に美しいウェブページとして表示するにはどうすればよいでしょうか? ここでNext.jsとNode.jsエコシステムの真価が発揮されます。

コースのコアファイルsrc/app/courses/[...slug]/page.tsxを開いてください。いくつかの強力なオープンソースライブラリをインポートしているのがわかります:

1. fspath(Node.js組み込みモジュール)

まず、動的ルーティングから渡されるslugパラメータを使って実際のファイルパスを構築し、fsでメモリに読み込みます。

import fs from "fs";
import path from "path";

// 仮にURLが/courses/vibe-tutor-web/01-introの場合
// slug配列は['vibe-tutor-web', '01-intro']になります
const dirPath = path.join(process.cwd(), "content", "courses", ...slug);
const filePath = dirPath + ".md";

const fileContents = fs.readFileSync(filePath, "utf8");

このコードはサーバーサイドで実行されるため、サーバーのファイルシステムに直接アクセスできます!

2. gray-matter(フロントマター解析)

Markdownファイルの先頭に---で囲まれたブロックがあるのに気づきましたか?

---
title: "第4章:Markdown動的レンダリング技術"
---

これはFront-matterと呼ばれ、通常YAML形式で記述され、記事の「メタデータ(Metadata)」(タイトル、著者、公開日など)を格納するために使用されます。 gray-matterライブラリを使うと、テキストファイルを「データ部分」と「コンテンツ部分」に簡単に分割できます:

import matter from "gray-matter";

const { data, content } = matter(fileContents);
// data.titleは"第4章:Markdown動的レンダリング技術"になります
// contentはMarkdown本文です

3. remarkremark-html(MarkdownからHTMLへの変換エンジン)

純粋なMarkdown本文を取得したら、最後のステップはそれをブラウザが理解できるHTMLに変換することです。 Unifiedエコシステムで最も有名なMarkdownプロセッサであるremarkを使用しています。

import { remark } from "remark";
import html from "remark-html";

const processedContent = await remark()
  .use(html, { sanitize: false }) 
  .process(content);

const contentHtml = processedContent.toString();

これで、contentHtmlは標準的なHTML文字列になりました!

4. dangerouslySetInnerHTML@tailwindcss/typography

最後に、これをReactコンポーネントに挿入します。Reactで生のHTML文字列を挿入するには、特別なプロパティであるdangerouslySetInnerHTMLを使用する必要があります。

<article className="prose prose-invert prose-blue max-w-none">
  <div dangerouslySetInnerHTML={{ __html: contentHtml }} />
</article>

ここで疑問に思うかもしれません:これらの変換されたHTML(<h1><p><code>など)にはTailwindのclassが適用されていないので、スタイルが全くない状態ではないか? 心配ありません!これが@tailwindcss/typographyプラグインの威力です! classNameproseを追加すると、このプラグインが自動的にすべてのHTML要素に最適な読みやすいスタイル(行間、フォントサイズ、リストのインデントなど)を適用します。 prose-invertはダークモードであることを伝え、テキストを反転処理します。prose-blueはすべてのリンクと引用符にブルーカラースキームを使用するように設定します。


動的目次生成システム

単一記事のレンダリングに加えて、Vibe Tutorには「自動目次生成」という魔法のような機能もあります。 URLに/courses/vibe-tutor-webとだけ入力すると、システムはこれがMarkdownファイルではなくディレクトリであることを認識します。 この時、私たちのプログラムは別のロジックをトリガーします:

  1. fs.readdirSyncを使ってディレクトリ内のすべての.mdファイルをスキャンします
  2. ファイルをアルファベット順に並べ替えます(これがファイル名を01-02-で始める理由です)
  3. 各ファイルのgray-matterからtitleを取得します
  4. 画面上に章番号付きの目次リンクリストを動的に生成します

これは、管理者であるあなたが新しいMarkdownファイルをディレクトリに追加するだけで、目次が自動更新され、記事が自動的に公開されることを意味します! これがMarkdownシステムがもたらす究極の効率性です。

次章では、このシステムの最も重要なビジネスロジック「Freemium権限制御メカニズム」について探求します。最初の数章を無料で公開して人を集め、後ろのコンテンツを有料でロックする方法とは?お楽しみに!

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!