Building a Git-Powered Website with Nuxt 3 Content
James Reed
Infrastructure Engineer · Leapcell

The Rise of Headless Content Management
In the rapidly evolving landscape of web development, the demand for flexible and efficient content management solutions continues to grow. Traditional Content Management Systems (CMS) often come with significant overhead, requiring databases, complex interfaces, and extensive server configurations. For many developers and projects, particularly those prioritizing speed, simplicity, and version control, a more lightweight and integrated approach is highly desirable. This is where the concept of "Git-driven content" shines. By leveraging the power of version control systems like Git and plain text formats such as Markdown, developers can achieve a streamlined workflow for managing website content. This approach not only simplifies content creation and updates but also integrates seamlessly with modern front-end frameworks.
Nuxt 3, a powerful and intuitive meta-framework for Vue.js, offers an exceptional toolset for building high-performance web applications. Among its many capabilities, the Nuxt Content module stands out as a game-changer for content-rich websites. This module elegantly transforms Markdown, YAML, CSV, and JSON files into a queryable data layer, making it incredibly easy to display and manage content without a traditional database. When combined with the principles of Git-driven content, Nuxt 3 Content empowers developers to build highly maintainable, version-controlled, and performant websites. This article will explore the practical implementation of Nuxt 3's Content module to construct a website whose content is entirely managed by Markdown files within a Git repository, offering a robust and developer-friendly content management solution.
Understanding the Core Concepts
Before we dive into the implementation, let's clarify some key terms that form the backbone of our discussion.
Nuxt 3: A full-stack web framework built on Vue 3, Nitropack, and Vite. It simplifies server-side rendering (SSR), static site generation (SSG), and offers an opinionated structure for building performant web applications.
Nuxt Content Module: An official Nuxt module that allows you to write content in various formats (Markdown, YAML, CSV, JSON) and access it through a powerful API. It automatically parses these files and provides a content query builder, making it easy to fetch and display content.
Markdown: A lightweight markup language for creating formatted text using a plain text editor. Its simplicity and readability make it ideal for content creation and version control.
Git-Driven Content: A content management strategy where all website content is stored as plain text files (e.g., Markdown) within a Git repository. Content changes are tracked, versioned, and collaborated on using standard Git workflows. This eliminates the need for separate databases or CMS interfaces for content.
Building a Git-Powered Website with Nuxt 3 Content
The fundamental principle behind a Git-driven website with Nuxt 3 Content is to treat your Markdown files in your project's repository as the single source of truth for your website's content. Nuxt Content then takes these files, parses them, and makes them available to your Vue components.
Setting Up Your Nuxt 3 Project
First, create a new Nuxt 3 project if you haven't already:
npx nuxi init git-content-blog cd git-content-blog npm install
Installing the Nuxt Content Module
Next, install the Nuxt Content module:
npm install --save-dev @nuxt/content
Then, add it to your nuxt.config.ts
:
// nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxt/content'] })
Structuring Your Content
The Nuxt Content module expects your content files to reside in a specific directory, typically content/
. Inside this directory, you can organize your Markdown files into subdirectories, which will dictate their URLs.
Let's create some example content for blog posts:
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.
Fetching and Displaying Content
Nuxt Content makes fetching content remarkably simple using queryContent()
and useAsyncData()
(or useFetch()
for client-side fetching).
pages/index.vue
(Listing all blog posts):
<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
(Displaying a single blog post):
To display individual blog posts, we'll use dynamic routing. The [slug]
in the file name tells Nuxt to create a route for each file under 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
(Displaying the about page):
<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 Integration and Workflow
The "Git-driven" aspect comes naturally. Your content/
directory is simply part of your Git repository.
- Content Creation/Editing: Content writers (or developers) edit Markdown files directly in the
content/
directory. - Version Control: All changes are committed to Git, providing a complete history, rollback capabilities, and collaborative workflows.
- Deployment: When your main branch is updated (e.g., via a pull request merge), your CI/CD pipeline triggers a rebuild and redeployment of your Nuxt application. During the build process, Nuxt Content parses the latest Markdown files.
This workflow means content management is entirely integrated with your development workflow, eliminating the need for a separate CMS interface.
Advanced Nuxt Content Features
-
ContentRenderer
andContentSlot
: These components allow you to render the Markdown content directly in your Vue components.ContentRenderer
automatically renders the parsed Markdown into HTML, whileContentSlot
allows injecting Vue components directly into your Markdown via custom syntax. -
Query Filtering and Sorting: Beyond
limit()
andsort()
, you can filter content using various conditions, for example,queryContent().where({ layout: 'post' }).find()
. -
Static Site Generation (SSG): For maximum performance and lower hosting costs, build your Nuxt application as a static site. Nuxt Content will generate all your pages during the build process, pre-rendering your Markdown content into HTML files.
// 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 } } })
To generate dynamic routes from content module use
_src/routes.ts
or_src/hooks.ts
withnitro
module. For Nuxt 3, Content Module automatically handles route generation for content files during SSG builds. You typically don't need explicitprerender.routes
for Content-generated paths unless you have specific edge cases.
The Simplicity of Git-Backed Content
The Nuxt 3 Content module offers an incredibly powerful yet simple way to manage website content by leveraging Markdown files stored in a Git repository. This approach aligns perfectly with modern development practices, promoting version control, collaboration, and a streamlined workflow. By understanding the core concepts and following the practical implementation steps, developers can build robust, high-performance websites where content management is as efficient and reliable as code management.
In essence, Nuxt 3 Content transforms your Git repository into a full-fledged, developer-friendly CMS, allowing you to focus on building great user experiences while your content is effortlessly managed and versioned.