Markdownブログ(多言語、タグ付き)
コンテンツはファイルであり、データベースの行ではありません。全体の設計は一つのアイデアに基づいています。
記事の識別子はtranslationKeyであり、スラッグではありません。 スラッグはSEOのためにローカライズされ、言語ごとに異なります。キーが、3つのファイルを1つの記事にし、hreflang、言語スイッチャー、関連記事、クロスロケールリダイレクトを可能にします。このフィールドを正しく設定すれば、残りはすべてルックアップです。
このモジュールを出荷したエンジニアによって執筆されました。監査対象となった以前の実装は、多言語で静的に生成されたマーケティングブログであり、サイトマップとSEOサーフェスを備えていました。以下の事実とルールは、コンテンツレイヤースイートとビルドが検証するものです。references/provenance.mdに記録があります。
使用する場合
- コンテンツがリポジトリ内のMarkdownとして作成され、アプリとともにデプロイされる
- 2つ以上の言語があり、言語ごとに異なるスラッグを使用する
- タグページ、関連記事、フィード、またはhreflangがスコープ内にある
- 全体をビルド時に静的に生成したい
使用しない場合
| 代わりにこれ | 代わりにこれを使用 |
|---|---|
| ホスト型CMSが信頼できる情報源である | そのCMSのSDKを使用。references/content-model.mdのみを保持 |
| ドキュメントサイト/ヘルプセンター(カテゴリ、サイドバー、検索) | ヘルプセンタースキル — 異なるナビゲーションモデル |
| 10記事未満の単一言語ブログ | インラインでfs + gray-matterを使用。これではオーバーヘッドが大きい |
| ユーザーが送信したMarkdownのレンダリング | サニタイズレンダラーを使用。references/rendering.mdのXSSに関する注意を参照 |
アーキテクチャ
content/blog/<locale>/<localized-slug>.md # フロントマター + 本文
|
v
parseFrontmatter --YAML、不正な引用符がある場合はラインパーサーにフォールバック
|
v
getAllPosts(locale) <-- 本番環境ではロケールごとにメモ化
|
+--> translationKey ----> 代替URL / hreflang / 言語切り替え
+--> tags -> タグのSLUG ---> タグページ(識別子はラベルではなくスラッグ)
+--> related (EN slugs) -> 関連記事、ENフォールバック付き
|
v
/blog /blog/[slug] /blog/tag/[slug] sitemap.xml feed.xml
重要な事実
- 関連記事はデフォルトロケールで一度だけ、デフォルトロケールのスラッグで作成されます。 翻訳ファイルは
relatedを完全に省略しても、translationKeyを通じて解決された正しい関連記事を取得できるため、すべての翻訳で同じ関連セクションがレンダリングされます。フォールバックテストがこれを保証します。 - タグの識別子はラベルではなく、スラッグです。
"AI Agents"と"AI agents"は同じURLにスラッグ化されます。スラッグでグループ化し、スラッグで投稿をマッチングするため、すべてのスペルバリアントの投稿が1つのタグページに表示されます。2つのテストがこれを保証します。 gray-matterは、引用符文字を含む二重引用符で囲まれたYAMLスカラーでエラーをスローします — 翻訳された抜粋に含まれるタイポグラフィ引用符が原因です。フォールバックパーサーがないと、ビルドがその1つのファイルで停止します。行単位のフォールバックにより読み込みが継続されます。ライブラリに対して検証済みであり、テストでカバーされています。gray-matterは、失敗したパースを空の結果としてキャッシュします。 パース前にキャッシュエントリを書き込むため、エラーをスローしたファイルの2回目以降のパースはすべて{}を返します — エラーもフロントマターもありません。常にmatter(contents, {})を呼び出してください。空のオブジェクトを渡すと、キャッシュが無効になります。- ページごとに、ロケールごとにO(投稿数 x ロケール数)で読み込まれます。メモ化しない場合。 ロケールごとにメモ化することで、各コンテンツファイルがビルドごとに1回だけ読み込まれます。
generateMetadataで代替URLを解決することは、以前の実装で測定された71倍のファイル読み込み増幅となります。 getPostSlugsは.mdをフィルタリングする必要があります。 このフィルタリングにより、.DS_Storeが.DS_Store.mdとして読み込まれ、ENOENTエラーになるのを防ぎます。テストがこれを保証します。- タグページにはクロスロケールの識別子がありません。 タグはロケールごとの自由テキストであるため、言語スイッチャーはタグURLで404になるURLを構築するのではなく、ブログインデックスにフォールバックする必要があります。
厳格なルール
クロスロケールのルックアップをスラッグで行わないでください。 スラッグは意図的にローカライズされています。
translationKeyが結合列であり、ロケール間でのスラッグの一致は偶然であり、関係ではありません。
getPostBySlugで暗黙的に下書きの可視性を決定しないでください。 ローダーで下書きをフィルタリングしてください。dynamicParams = falseは副作用としてのみそれらを非表示にし、フラグが変更されるとすぐにその効果がなくなります。下書きテストがフィルタを保持します。
「5分で読める」などの定数をレンダリングしないでください。 本文から計算してください。
split1回で済み、読了時間テストがそれを保持します。
読み込み時にラベルからタグページのURLを導出し、レンダリング時にURLからラベルを導出する場合、衝突チェックなしに行わないでください。 そのラウンドトリップは、すべてのラベルが一意にスラッグ化されている場合にのみ可逆的です。
クイックスタート
- ロケール契約とフロントマタースキーマを設定 — content-model.md
- ローダーを構築(メモ化、
.mdフィルタリング、フォールバックパーサー) — content-loader.md - ロケール、代替URL、クロスロケールリダイレクトを配線 — i18n-and-routing.md
- タググループ化とタグページを追加 — tags.md
- 3つのルート、メタデータ、JSON-LDを構築 — pages-and-seo.md
- 本文とカバーをレンダリング — rendering.md
- バリデーションスクリプトを追加し、運用上のギャップを読む — operations.md
- フィクスチャとテストを実行 — testing.md
既存のアプリに移植しますか?最初にadaptation.mdのシームテーブルを埋めてください — 10分で完了し、リネーム作業を節約できます。
リファレンスディレクトリ
| シナリオ | トリガーキーワード | リファレンス |
|---|---|---|
| フロントマターフィールド、下書き、翻訳キー、関連記事 | frontmatter, schema, translationKey, draft, related, excerpt | content-model.md |
| ファイル読み込み、キャッシュ、YAMLエラー、読了時間 | gray-matter, fs, cache, memoize, ENOENT, YAMLException, parse | content-loader.md |
| ロケール、hreflang、代替URL、誤ったロケールへのリンク | i18n, locale, hreflang, alternates, canonical, language switcher, redirect | i18n-and-routing.md |
| タグURL、衝突、薄いタグページ | tag, slug, diacritics, collision, tag page, taxonomy | tags.md |
| ルート、メタデータ、JSON-LD、サイトマップ、RSS | page.tsx, generateStaticParams, generateMetadata, dynamicParams, sitemap, RSS, feed, BlogPosting | pages-and-seo.md |
| MarkdownからReactへ、コードブロック、カバー画像 | react-markdown, remark-gfm, syntax highlighting, cover, motif, accent, XSS | rendering.md |
| コンテンツ検証、ビルドコスト、エディタワークフロー | validation, CI check, build time, drafts, authoring, operator | operations.md |
| フィクスチャと合格テストスイート | test, fixture, vitest, node:test, regression, assert | testing.md |
| ホストアプリへの適合:シーム、リネーム、プローブ | adapt, port, host, seam, rename, CMS, integrate | adaptation.md |
| 監査台帳:以前の実装と比較して何が変更され、保持され、追加されたか | provenance, deviations, fidelity, audit, ledger | provenance.md |