FastAPIでDocusaurus風サイトを構築:ステップ4 - Frontmatterの解析

Cover

前の記事では、Markdownコードブロックの構文ハイライトを追加しました。

しかし、main.pyのルーティング関数("page_title": "Hello, Markdown!")のドキュメントページタイトル({{ page_title }})がまだハードコーディングされていることに気づいたかもしれません。これは非常に柔軟性がありません。新しいドキュメントを追加するたびにコードを変更する必要があるということでしょうか?

ドキュメンテーションサイトは柔軟である必要があり、記事をいつでも追加または削除できる必要があります。記事のメタデータ(タイトル、著者、日付など)は、コンテンツと同じように、Markdownファイル自体で定義する必要があります。

この記事では、Frontmatter(Markdownファイルの先頭にメタデータを定義するための一般的な仕様)を紹介し、FastAPIがそれを解析できるようにすることで、メタデータを動的にロードできるようにします。

ステップ1:Frontmatter解析ライブラリのインストール

python-frontmatterを使用して、ファイルからFrontmatterとMarkdownコンテンツを分離および解析します。

以下のコマンドでインストールします:

pip install python-frontmatter

ステップ2:MarkdownドキュメントにFrontmatterを追加する

次に、docs/hello.mdファイルを変更して、先頭にFrontmatterを追加しましょう。

Frontmatterブロックは3つのダッシュ---)で囲まれます。

docs/hello.mdを更新する:

---
title: Hello, Frontmatter!
author: FastAPI Developer
date: 2025-11-09
---

... (Markdownコンテンツの残りの部分)

ここでは、titleauthordateの3つのメタデータフィールドを定義しました。必要に応じてさらにフィールドを追加できます。titleフィールドは最終的に記事のpage_titleとして使用されます。

ステップ3:Frontmatterを解析するようにmain.pyを変更する

単純なopen().read()の代わりに、frontmatterライブラリを使用してファイルをロードするようにget_hello_docルーティング関数を変更します。

frontmatter.load()関数は、ファイルを2つの部分に解析します:

  1. post.metadata:すべてのFrontmatterデータを含む辞書(例:{'title': 'Hello, Frontmatter!', ...})。
  2. post.contentMarkdownコンテンツの本体のみを含む文字列。

main.pyを開き、次のように変更します:

# main.py
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
import markdown
from fastapi.staticfiles import StaticFiles
import frontmatter  # 1. Frontmatterライブラリをインポート

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

templates = Jinja2Templates(directory="templates")


# --- ホームルート(変更なし) ---
@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
    context = {
        "request": request,
        "page_title": "Hello, Jinja2!" # (画像と一貫性を保つために元の中国語を保持していますが、「Hello, Jinja2!」が英語の同等物です)
    }
    return templates.TemplateResponse("index.html", context)


# --- 2. ドキュメントルートを変更する ---
@app.get("/docs/hello", response_class=HTMLResponse)
async def get_hello_doc(request: Request):
    """
    hello.mdドキュメントを読み込み、解析(Frontmatterを含む)、レンダリングする
    """
    md_file_path = "docs/hello.md"

    try:
        # 3. frontmatter.loadを使用してファイルを読み込み、解析する
        post = frontmatter.load(md_file_path)
    except FileNotFoundError:
        return HTMLResponse(content="<h1>404 - Document Not Found</h1>", status_code=404)
    except Exception as e:
        # 無効なYAMLの場合の一般的なエラーハンドリングを追加する
        return HTMLResponse(content=f"<h1>500 - Parse Error: {e}</h1>", status_code=500)

    # 4. メタデータとコンテンツを抽出する
    metadata = post.metadata
    md_content = post.content # これは純粋なMarkdownコンテンツです

    # 5. Markdownコンテンツのみを変換する
    extensions = ['fenced_code', 'codehilite']
    html_content = markdown.markdown(md_content, extensions=extensions)

    # 6. メタデータからpage_titleを動的に取得する
    #    'title'キーが存在しない場合にクラッシュしないように.get()を使用する
    page_title = metadata.get('title', 'Untitled Document')

    context = {
        "request": request,
        "page_title": page_title,    # ハードコーディングされた値を置き換えた
        "content": html_content
    }

    return templates.TemplateResponse("doc.html", context)

ステップ4:実行してテストする

uvicorn main:app --reloadを実行してサーバーを起動します。

http://127.0.0.1:8000/docs/helloにアクセスします。

ブラウザのタブタイトルとページ上の<h1>タグが、「Hello, Markdown!」ではなく、「Hello, Frontmatter!」(hello.mdファイルのFrontmatterで定義したもの)に置き換わっていることがわかります。

結論と次のステップ

Frontmatterとその解析ロジックを導入することで、記事はサイトから完全に分離されました。

Markdown以外にも、Webサイトには画像などの静的ファイルが含まれます。FastAPIがこれらの静的ファイルを正しくデプロイし、オンラインでアクセスできるようにするにはどうすればよいでしょうか?

次の記事では、この問題を解決します。Markdownファイルで参照される静的アセット(画像など)を処理し、FastAPIによって正しくデプロイされ、最終的なWebページに表示されるようにします。

その他

サイトを構築した後、オンラインで他の人に見てもらえるようにデプロイしたいと思うかもしれません。しかし、ほとんどのクラウドプラットフォームは高価であり、このような練習プロジェクトのために高額を支払う価値はありません。

もっと経済的なデプロイ方法はありませんか?Leapcellを試してみてください。Python、Node.js、Go、Rustなどの複数の言語のデプロイをサポートしており、毎月寛大な無料枠を提供しているため、費用をかけずに最大20個のプロジェクトをデプロイできます。

Leapcell


Xでフォローする:@LeapcellJapan


本シリーズの他の記事を読む

関連記事: