Next.js、Contentlayer、Git Submodulesでポートフォリオを構築した方法

Table of Contents
個人の開発者ポートフォリオを構築することは、エンジニアリングプロジェクトの中でも最もやりがいのあるものの一つです。それはあなたの技術的なサンドボックスであり、最先端のウェブ標準を試したり、深い技術ガイドを共有したり、完全に自分で所有する高性能なデジタルプレゼンスを構築したりする場所です。
locionic.com を設計するにあたり、私は3つの厳格なエンジニアリング制約を設けました。
- データベースとヘッドレスCMSをゼロに: 月額のSaaSデータベース料金、外部APIのダウンタイム、ベンダーロックインを排除。
- コンテンツをコードとして扱う: すべての技術記事、コードスニペット、チュートリアルは、Gitバージョン管理下のプレーンなMarkdown/MDXとして存在すること。
- グローバルなサブ秒パフォーマンス: 完璧な100/100のCore Web Vitals、事前レンダリングされた静的HTML、クライアントサイドのレイアウトシフトをゼロに。
このガイドでは、Gitサブモジュールによるコンテンツ分離とContentlayerのビルドスキーマから、React Server Component (RSC) ペイロードの最適化に至るまで、locionic.comの背後にある正確な本番アーキテクチャを詳しく解説します。
1. コンテンツをコードとして扱うアーキテクチャ: Gitサブモジュールによる分離
ブログを構築するほとんどの開発者は、Markdownファイルをメインのアプリケーションリポジトリに直接保存するか、SanityやStrapiのような外部のヘッドレスCMSに接続します。どちらのアプローチにも顕著な欠点があります。
- モノリシックなアプリリポジトリ: ブログが画像やローカライズされた翻訳(
en、vi、ja)を含む200以上の記事に拡大すると、コードリポジトリのGit履歴が数千のコンテンツコミットで肥大化します。 - ヘッドレスCMS: ビルド中に継続的なネットワーク呼び出し、複雑なWebhook同期パイプライン、月額サブスクリプションティアが必要になります。
サブモジュールソリューション
私はコードベースを2つの異なるリポジトリに分離しました。
- アプリケーションシェル (
locionic/blog-and-projects): Next.jsのルート、Tailwindのスタイリング、Reactコンポーネント、ビルドスクリプトが含まれます。 - コンテンツコア (
locionic/projectsはcontents/にマウント): 純粋な.mdxファイル、技術チュートリアル、インタラクティブなチートシート、ローカライズされた翻訳のみが含まれます。
/home/developer/blog-and-projects/
├── app/ # Next.js 14 App Router (RSC Layouts, API routes)
├── components/ # UI components, MDX custom widgets
├── contents/ # Git Submodule pointing to locionic/projects
│ ├── blog_dev/ # 200+ Production Technical Guides (.mdx)
│ ├── cheatsheets/ # Interactive CLI & Git Reference Guides
│ └── courses/ # Multi-lesson curriculum files
└── package.json
この分離されたアーキテクチャにより、アプリケーションのリビルドをトリガーしたり、Reactコードに触れたりすることなく、任意のMarkdownエディタやモバイルGitクライアントから技術ガイドを執筆、編集、公開できます。
2. Contentlayerによる型安全なコンテンツ処理
生の.mdxファイルをビルド時に厳密に型付けされたTypeScriptオブジェクトに変換するために、このサイトではContentlayerを利用しています。
// contentlayer.config.ts
import { defineDocumentType, makeSource } from 'contentlayer/source-files';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypePrismPlus from 'rehype-prism-plus';
export const Blog = defineDocumentType(() => ({
name: 'Blog',
filePathPattern: 'blog_dev/**/*.mdx',
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
date: { type: 'date', required: true },
summary: { type: 'string', required: true },
tags: { type: 'list', of: { type: 'string' }, default: [] },
draft: { type: 'boolean', default: false },
images: { type: 'list', of: { type: 'string' }, default: [] },
},
computedFields: {
slug: {
type: 'string',
resolve: (doc) => doc._raw.flattenedPath.replace(/^blog_dev\//, '').replace(/\/[^/]+$/, ''),
},
readingTime: {
type: 'json',
resolve: (doc) => readingTime(doc.body.raw),
},
},
}));
Contentlayerはすべての.mdxファイルを読み込み、YAMLフロントマターを解析し、型を検証し、.contentlayer/generatedに静的なJSONドキュメントを生成します。もし私がsummaryが欠落している記事や、dateが不正な記事をコミットした場合、CIでビルドが即座に失敗し、正確な行番号が示されます。
3. RSCシリアライゼーションの罠: 2MBの隠れたページウェイトを削減
本番プロファイリング中に発見された最も重要なパフォーマンスバグの1つは、React Server Components (RSC) ペイロードのシリアライゼーションに関するものでした。
初期のビルドでは、ブログ記事のレイアウトは各記事の下部に「関連投稿」ウィジェットを表示していました。
// ❌ DANGEROUS: Leaking 2MB of MDX body code into every page's RSC payload
import { allBlogs } from 'contentlayer/generated';
export default function BlogPostPage({ params }) {
const post = allBlogs.find((p) => p.slug === params.slug);
// Passing full Contentlayer objects to a Client Component!
return <PostLayout post={post} allPosts={allBlogs} />;
}
allBlogsには200以上の全記事の生のコンパイル済みJavaScriptコード(body.code)が含まれているため、Next.jsは200記事のカタログ全体をすべてのブログ記事の__NEXT_DATA__ / RSC JSONペイロードにシリアライズしていました。単純な記事ページが初回ロード時に2.3 MBの隠れたJSONをダウンロードしていたのです!
解決策: coreContent()によるサーバーサイドでのストリッピング
サーバーとクライアントの境界を越えてプロパティをシリアライズする前に、ビルド時のフィールドをすべて削除するようにデータレイヤーをリファクタリングしました。
// ✅ OPTIMAL: Only ship lightweight metadata to client components
export function coreContent<T extends { body: unknown; _raw: unknown }>(content: T) {
const { body, _raw, _id, type, ...rest } = content;
return rest;
}
// Pass only 3 slim related post summaries
const relatedPosts = getRelatedPosts(post, allBlogs, 3).map(coreContent);
この単一の最適化により、初期ページウェイトは2.3 MBから82 KBに削減され、帯域幅が96%削減され、モバイルのLighthouseスコアが99に急上昇しました。
4. アルゴリズムによる内部リンク: 10クラスターのハミルトン環
検索エンジンの孤立ペナルティを回避し、トピックの権威を最大化するために、このサイトでは自動グラフ理論スクリプト(scripts/relink_cluster_mesh.py)を実行しています。
- TF-IDF意味的クラスタリング: 全219記事を10の異なる技術クラスター(
nextjs_react、ai_llm_rag、devops_cloud、systems_wasm)に分類します。 - ハミルトン4弦環: 各クラスター内で連続した閉じた環を形成し、各記事を最も近い4つの意味的隣接記事に接続します。
- 保証されたトポロジー: すべての記事が少なくとも4つの入次数と出次数を持つことを保証し、ドメイン全体で孤立記事を正確に0に保ちます。
技術アーキテクチャの概要
| アーキテクチャ層 | 選択された技術 | 主なエンジニアリング上の理由 |
|---|---|---|
| フレームワーク | Next.js 14 App Router | 静的サイト生成 (SSG) + サーバーコンポーネント |
| スタイリング | Tailwind CSS + Typography | ランタイムCSS-in-JSオーバーヘッドゼロ |
| コンテンツエンジン | Contentlayer 0.3.4 | MDXのビルド時型検証 |
| 検索エンジン | FlexSearch / 静的ビルド | Algoliaのコストなしで100%クライアントサイド検索 |
| ホスティング & CDN | Vercel Edge Network | グローバルHTTP/3キャッシングとEdge OG画像レンダリング |
| アナリティクス & プライバシー | Google Analytics + CookieConsent | GDPR準拠のクッキー同意フラグ |
よくある質問
データベースなしでクライアントサイド検索はどのように機能しますか?
ビルド後のステップで、scripts/generate-search.mjsは、公開されたすべての記事のタイトル、要約、タグを含むミニファイされたpublic/search.jsonファイルをコンパイルします。ユーザーがCmd+Kを押すと、検索モーダルがこのインデックスをメモリにロードし、サブミリ秒のレイテンシで即座に検索します。
ソーシャルOpenGraphプレビュー画像はどのように生成されますか?
Next.jsのEdgeルート/api/og/[locale]/[slug]を利用しています。@vercel/og(Satoriをバックエンドとする)を使用して、サーバーは投稿タイトル、著者名、ブランドを含む動的で高解像度の1200x630 PNG画像をオンザフライで生成し、1年間の積極的な不変エッジキャッシングを行います。
コードのシンタックスハイライトはどのように処理していますか?
MDXのコードブロックは、カスタムのPrism CSSテーマを使用してrehype-prism-plusでビルド時に事前レンダリングされます。これにより、シンタックスハイライト用のJavaScriptはブラウザに一切送信されず、シンタックストークンはネイティブHTMLの<span>要素に事前フォーマットされます。
こちらもおすすめです
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Next.js App RouterとPrisma: セットアップとコネクションプーリング
App RouterでのNext.jsとPrismaの完全ガイドで、グローバルなコネクションプール枯渇の防止、型安全なクエリ、サーバーアクションミューテーション、シードスクリプトについて解説します。
Read more
Next.jsにおけるReactハイドレーションエラー418の修正:テキスト不一致と拡張機能(2026年版)
Next.jsのハイドレーションエラーを解決する2026年版完全ガイドで、Minified React Error #418、Error #423、Text Content Mismatch、window/localStorageのSSRバグをコピペコードとライブデバッガーで修正しましょう。
Read more
Next.jsにおけるsuppressHydrationWarning: 安全な利用法とデバッグの完全ガイド
Next.jsのsuppressHydrationWarningについて、安全な利用法とデバッグ方法を実証済みの本番環境での例を交えて網羅的に解説する包括的なガイドです。
Read more