Nuxt 3 ContentとGitを活用したウェブサイト構築
James Reed
Infrastructure Engineer · Leapcell

ヘッドレスCMSの台頭
進化し続けるウェブ開発の世界では、柔軟で効率的なコンテンツ管理ソリューションへの需要が高まり続けています。従来のコンテンツ管理システム(CMS)は、データベース、複雑なインターフェース、広範なサーバー構成を必要とし、しばしば大きなオーバーヘッドを伴います。速度、シンプルさ、バージョン管理を優先する多くの開発者やプロジェクトにとって、より軽量で統合されたアプローチが強く望まれます。そこで「Git駆動型コンテンツ」の概念が輝きます。Gitのようなバージョン管理システムやMarkdownのようなプレーンテキスト形式を活用することで、開発者はウェブサイトのコンテンツを管理するための合理化されたワークフローを実現できます。このアプローチは、コンテンツの作成と更新を簡素化するだけでなく、最新のフロントエンドフレームワークともシームレスに統合されます。
Vue.jsのための強力で直感的なメタフレームワークであるNuxt 3は、高性能なウェブアプリケーションを構築するための優れたツールセットを提供します。その多くの機能の中でも、Nuxt Contentモジュールはコンテンツリッチなウェブサイトにとってゲームチェンジャーとして際立っています。このモジュールは、Markdown、YAML、CSV、JSONファイルをクエリ可能なレイヤにエレガントに変換し、従来のデータベースなしでコンテンツを表示・管理することを非常に容易にします。Git駆動型コンテンツの原則と組み合わせることで、Nuxt 3 Contentは開発者が高度に保守可能で、バージョン管理され、パフォーマンスの高いウェブサイトを構築することを可能にします。この記事では、Nuxt 3のContentモジュールを実際に実装して、Gitリポジトリ内のMarkdownファイルによって完全に管理されるウェブサイトを構築する方法を探ります。これにより、堅牢で開発者に優しいコンテンツ管理ソリューションが提供されます。
コアコンセプトの理解
実装に入る前に、議論の基盤となるいくつかの重要な用語を明確にしましょう。
Nuxt 3: Vue 3、Nitro、Viteを基盤としたフルスタックウェブフレームワークです。サーバーサイドレンダリング(SSR)、静的サイトジェネレーション(SSG)を簡素化し、パフォーマンスの高いウェブアプリケーションを構築するための意見に沿った構造を提供します。
Nuxt Contentモジュール: 様々なフォーマット(Markdown、YAML、CSV、JSON)でコンテンツを記述し、強力なAPIを通じてアクセスできる公式Nuxtモジュールです。これらのファイルを自動的に解析し、コンテンツクエリビルダーを提供するため、コンテンツの取得と表示が容易になります。
Markdown: プレーンテキストエディタを使用してフォーマットされたテキストを作成するための軽量マークアップ言語です。そのシンプルさと可読性により、コンテンツ作成とバージョン管理に最適です。
Git駆動型コンテンツ: ウェブサイトのすべてのコンテンツがGitリポジトリ内のプレーンテキストファイル(例:Markdown)として保存されるコンテンツ管理戦略です。コンテンツの変更は、標準的なGitワークフローを使用して追跡、バージョン管理、共同作業が行われます。これにより、個別のデータベースやCMSインターフェースが不要になります。
Nuxt 3 ContentによるGit駆動型ウェブサイトの構築
Nuxt 3 ContentによるGit駆動型ウェブサイトの基本原則は、プロジェクトのリポジトリ内のMarkdownファイルをウェブサイトコンテンツの単一の真実の情報源として扱うことです。Nuxt Contentはこれらのファイルを解析し、Vueコンポーネントで利用できるようにします。
Nuxt 3プロジェクトのセットアップ
まず、まだNuxt 3プロジェクトを作成していない場合は、作成します。
npx nuxi init git-content-blog cd git-content-blog npm install
Nuxt Contentモジュールのインストール
次に、Nuxt Contentモジュールをインストールします。
npm install --save-dev @nuxt/content
そして、nuxt.config.ts
に追加します。
// nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxt/content'] })
コンテンツの構造化
Nuxt Contentモジュールは、コンテンツファイルを特定のディレクトリ、通常はcontent/
内に配置することを期待しています。このディレクトリ内で、Markdownファイルをサブディレクトリに整理して、それらのURLを決定できます。
ブログ投稿の例としていくつかのコンテンツを作成しましょう。
content/
├── blog/
│ ├── first-post.md
│ ├── second-post.md
│ └── third-post.md
└── about.md
content/blog/first-post.md
:
--- title: My First Blog Post date: 2023-10-26 author: John Doe description: This is my very first blog post exploring the world of Nuxt 3 and Git-driven content. --- ## Welcome to My Blog! This post marks the beginning of an exciting journey into web development. I'm thrilled to share my thoughts and experiences with you. ### What is Markdown? Markdown is a lightweight markup language that allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid HTML.
content/blog/second-post.md
:
--- title: Understanding Nuxt Content Queries date: 2023-11-01 author: Jane Smith description: A deep dive into how to effectively query content using the Nuxt Content module. --- ## Powerful Content Queries Nuxt Content provides a flexible API to fetch and filter your content. You can query based on file path, frontmatter properties, and more. ### Example Query ```vue <script setup> const { data: posts } = await useAsyncData('blog-posts', () => queryContent('blog') .limit(5) .sort({ date: -1 }) .find() ); </script>
**`content/about.md`:**
```markdown
---
title: About Us
layout: page
---
## Our Story
We are a passionate team dedicated to building amazing web experiences with modern technologies like Nuxt 3.
### Our Mission
To empower developers with accessible and efficient tools for content management.
コンテンツの取得と表示
Nuxt Contentは、queryContent()
とuseAsyncData()
(またはクライアントサイドでの取得にはuseFetch()
)を使用してコンテンツの取得を非常に簡単なものにします。
pages/index.vue
(すべてのブログ投稿を一覧表示):
<template> <div class="container mx-auto px-4 py-8"> <h1 class="text-4xl font-bold mb-8">My Git-Powered Blog</h1> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <NuxtLink v-for="post in posts" :key="post._path" :to="post._path" class="block p-6 border rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300"> <h2 class="text-2xl font-bold mb-2">{{ post.title }}</h2> <p class="text-gray-600 text-sm mb-4">By {{ post.author }} on {{ new Date(post.date).toLocaleDateString() }}</p> <p class="text-gray-700">{{ post.description }}</p> </NuxtLink> </div> </div> </template> <script setup> const { data: posts } = await useAsyncData('blog-posts', () => queryContent('blog') .sort({ date: -1 }) // Sort by date in descending order .find() ); </script> <style> /* Basic Tailwind CSS setup for demonstration */ body { font-family: sans-serif; } </style>
pages/blog/[slug].vue
(単一のブログ投稿を表示):
個々のブログ投稿を表示するには、動的ルーティングを使用します。ファイル名にある[slug]
は、Nuxtがcontent/blog/
の下の各ファイルに対するルートを作成することを意味します。
<template> <div class="container mx-auto px-4 py-8 max-w-2xl"> <ContentDoc v-if="post" class="prose lg:prose-xl mx-auto"> <h1>{{ post.title }}</h1> <p class="text-gray-600 text-sm">By {{ post.author }} on {{ new Date(post.date).toLocaleDateString() }}</p> <ContentRenderer :value="post" /> </ContentDoc> <div v-else> <p>Post not found.</p> </div> </div> </template> <script setup> const route = useRoute(); const { data: post } = await useAsyncData(`blog-post-${route.params.slug}`, () => queryContent('blog', route.params.slug).findOne() ); </script> <style> /* Add some basic styling for the prose content */ .prose h1 { font-size: 2.5rem; margin-bottom: 1rem; } .prose h2 { font-size: 2rem; margin-top: 2rem; margin-bottom: 1rem; } .prose p { margin-bottom: 1rem; line-height: 1.6; } /* You would typically use a typography plugin like @tailwindcss/typography here */ </style>
pages/about.vue
(aboutページを表示):
<template> <div class="container mx-auto px-4 py-8 max-w-2xl"> <ContentDoc v-if="page" class="prose lg:prose-xl mx-auto"> <h1>{{ page.title }}</h1> <ContentRenderer :value="page" /> </ContentDoc> <div v-else> <p>About page not found.</p> </div> </div> </template> <script setup> const { data: page } = await useAsyncData('about-page', () => queryContent('about').findOne() ); </script>
Git統合とワークフロー
「Git駆動型」という側面は自然に生まれます。content/
ディレクトリは、単にGitリポジトリの一部であるだけです。
- コンテンツ作成/編集: コンテンツライター(または開発者)は、
content/
ディレクトリ内のMarkdownファイルを直接編集します。 - バージョン管理: すべての変更はGitにコミットされ、完全な履歴、ロールバック機能、共同作業ワークフローを提供します。
- デプロイ: メインブランチが更新されると(例:プルリクエストのマージ経由)、CI/CDパイプラインがNuxtアプリケーションのリビルドと再デプロイをトリガーします。ビルドプロセス中に、Nuxt Contentは最新のMarkdownファイルを解析します。
このワークフローは、コンテンツ管理が開発ワークフローに完全に統合されていることを意味し、個別のCMSインターフェースが不要になります。
Nuxt Contentの高度な機能
ContentRenderer
およびContentSlot
: これらのコンポーネントを使用すると、Vueコンポーネント内でMarkdownコンテンツを直接レンダリングできます。ContentRenderer
は解析されたMarkdownを自動的にHTMLにレンダリングし、ContentSlot
はカスタム構文を介してVueコンポーネントをMarkdownに直接挿入することを可能にします。- クエリフィルタリングとソート:
limit()
およびsort()
に加えて、さまざまな条件でコンテンツをフィルタリングできます。たとえば、queryContent().where({ layout: 'post' }).find()
です。 - 静的サイトジェネレーション(SSG): 最大限のパフォーマンスと低いホスティングコストのために、Nuxtアプリケーションを静的サイトとしてビルドします。Nuxt Contentはビルドプロセス中にすべてのページを生成し、MarkdownコンテンツをHTMLファイルにプリレンダリングします。
// nuxt.config.ts export default defineNuxtConfig({ ssr: true, // Enable SSR for pre-rendering // Generate all routes that Nuxt Content finds nitro: { prerender: { routes: ['/'] // Ensure the index route is prerendered } } })
動的ルートをコンテンツモジュールから生成するには、nitro
モジュールを使用して_src/routes.ts
または_src/hooks.ts
を使用します。
Nuxt 3では、ContentモジュールはSSGビルド中にコンテンツファイルのルート生成を自動的に処理します。特定の例外的なケースがない限り、通常、Contentによって生成されたパスに対して明示的なprerender.routes
は必要ありません。
Gitバックエンドコンテンツのシンプルさ
Nuxt 3 Contentモジュールは、Gitリポジトリに保存されたMarkdownファイルを利用してウェブサイトコンテンツを管理するための、信じられないほど強力でありながらシンプルな方法を提供します。このアプローチは、バージョン管理、共同作業、合理化されたワークフローを促進する最新の開発プラクティスと完全に一致します。コアコンセプトを理解し、実践的な実装手順に従うことで、開発者はコンテンツ管理がコード管理と同じくらい効率的で信頼性の高い、堅牢で高性能なウェブサイトを構築できます。
本質的に、Nuxt 3 ContentはGitリポジトリを本格的な開発者向けCMSに変換し、コンテンツが楽に管理されバージョン管理される間、開発者が素晴らしいユーザーエクスペリエンスの構築に集中できるようにします。