7 min read
Building a Blog with Nuxt 3
Nuxt 3 is the latest major release of the popular Vue.js framework Nuxt.js. It comes with several improvements and new features that make it easier than ever to build fast, SEO-friendly web applications. In this comprehensive tutorial, we will walk through how to build a blog from scratch using Nuxt 3.
Overview of Nuxt 3
Nuxt 3 is a complete rewrite of Nuxt.js using Vue 3 and Vite under the hood. Some of the key improvements and new features in Nuxt 3 include:
- Built on top of Vite for faster development and HMR
- Uses Vue 3 with Composition API as the default
- File-based routing system
- Modules are renamed to App Services for better semantics
- New Schema-based page fetching with AsyncData 2.0
- Auto importing of components
- Improved developer experience with IntelliSense, autocompletion etc.
Overall, Nuxt 3 provides a more modular and flexible architecture while retaining the core features that made Nuxt popular - server-side rendering, powerful routing, and static site generation.
Project Setup
Let's start by creating a new Nuxt 3 project:
npm create nuxt-app nuxt-blog
Choose the following options during project setup:
- Package manager: Npm
- UI framework: None
- Nuxt modules: Content
- Version control system: Git
This will generate a basic Nuxt 3 project structure with the content
module installed. The content module allows managing Markdown/JSON/YAML/CSV content seamlessly in Nuxt apps. We'll use it later for our blog posts.
Next, install the dependencies:
cd nuxt-blog
npm install
and run the dev server:
npm run dev
Our basic Nuxt blog skeleton is now ready!
Routes and Pages
For any blog or content site, routes and pages form the core. Nuxt offers a file-system based routing system that makes it easy to organize the app pages.
First, let's setup the main routes needed for our blog:
/
- The homepage/posts
- List all blog posts/posts/:slug
- Individual blog post page/about
- Simple about us page
To define these routes, we can create corresponding .vue
pages inside the pages/
directory:
pages/
|- index.vue
|- posts/
|- index.vue
|- posts/_slug.vue
|- about.vue
Nuxt will automatically map these component files to routes as per the file structure.
Let's add some basic content to these pages for now:
index.vue
<template>
<div>
<h1>Homepage</h1>
</div>
</template>
posts/index.vue
<template>
<div>
<h1>Blog Posts</h1>
</div>
</template>
posts/_slug.vue
<template>
<div>
<h1>Post Page</h1>
</div>
</template>
about.vue
<template>
<div>
<h1>About Us</h1>
</div>
</template>
Now if you navigate to http://localhost:3000
and click around, you'll see the basic routes in action!
Layouts
Every site needs a consistent layout across pages. In Nuxt, layouts can be defined as components and wrapped around page components.
Let's create a layout in layouts/default.vue
:
<template>
<div>
<header>
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/about">About</NuxtLink>
</header>
<slot />
</div>
</template>
This adds a header with navigation links and uses the <slot/>
component to display the page components.
To apply this layout, open nuxt.config.js
and update the layouts
section:
export default {
layouts: {
default: "layouts/default",
},
};
Now our layout will be applied across all routes!
Dynamic Routes and Params
We defined the posts/_slug.vue
route using an underscore prefix to denote that it's a dynamic route. This allows capturing the post slug from the url.
For example, /posts/my-first-post
will be mapped to posts/_slug.vue
and the slug param will be passed in the context object.
To access it, first we need to export the route definition from pages/posts/_slug.vue
:
export default {
// bind slug param to props
props: ["slug"],
};
Then access it in the template:
<template>
<div>
<h1>Post: {{ slug }}</h1>
</div>
</template>
Now when you navigate to /posts/test
, you can see the slug param being passed properly!
Global Styling and Assets
For any blog or website, consistent styling across pages is important. Nuxt provides different ways to add styles - CSS files, pre-processors like Sass, CSS-in-JS etc.
For this tutorial, let's keep it simple and add a global CSS file. Create a assets/main.css
file:
body {
font-family: "Segoe UI", sans-serif;
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
a {
text-decoration: none;
color: teal;
}
To include this CSS file globally, open nuxt.config.js
and update the css
section:
export default {
css: ["~/assets/main.css"],
};
This will automatically import it across all pages.
We can also add static image assets that can be imported into components later. For example, create an assets/images
folder with a placeholder logo logo.png
.
Blog Post Content with Markdown
It's time to create our blog content! For a real app, content will come from a headless CMS. But for this demo, we'll manage posts as Markdown files manually.
The content module we installed earlier allows us to work easily with Markdown and other content formats. To manage Markdown content for our blog posts, first create a content/posts
folder.
Then add a sample Markdown file first-post.md
:
---
title: My First Blog Post
description: Learning how to use @nuxt/content to create a blog
img: first-post.jpg
alt: my first post
---
# My First Blog Post
Welcome to my first blog post using content module
Some key things to note:
- The top contains YAML front matter for metadata
- The body has actual Markdown content
Let's add one more sample post second-post.md
:
---
title: My Second Blog Post
description: Creating a simple blog with Nuxt
img: second-post.jpg
alt: my second post
---
# My Second Blog Post
This is the second blog post. I'm learning Nuxt Content :)
Now we have some dummy content for our blog. Later we'll learn how to query and display these posts dynamically.
Homepage and Blog Listing
Let's build out the rest of the site now that routes, layouts, assets and content are ready.
First, update the homepage at pages/index.vue
to show a list of recent blog posts:
<template>
<div>
<h1>My Nuxt Blog</h1>
<div v-for="post in posts">
<NuxtLink :to="`/posts/${post.slug}`"> {{ post.title }} </NuxtLink>
</div>
</div>
<!-- fetch posts -->
</template>
<script>
export default {
async asyncData() {
const posts = await queryContent("/posts")
.only(["title", "slug"])
.sortBy("createdAt", "desc")
.limit(5)
.find();
return {
posts,
};
},
};
</script>
We are using asyncData
to query only needed fields from Markdown content, sort by latest, and limit to 5 posts.
This will list post titles linked to each post route dynamically!
For the main posts list page, update posts/index.vue
similarly:
<template>
<div>
<h1>All Blog Posts</h1>
<div v-for="post in posts">
<NuxtLink :to="`/posts/${post.slug}`"> {{ post.title }} </NuxtLink>
</div>
</div>
<!-- fetch posts -->
</template>
<script>
export default {
async asyncData() {
const posts = await queryContent("/posts")
.sortBy("createdAt", "desc")
.find();
return {
posts,
};
},
};
</script>
This will display a full list of blog posts sorted by latest.
Post Page
Now let's display the full post content on post pages at posts/_slug.vue
.
To get the post data based on the slug, we use:
const post = await queryContent("/posts")
.where({ slug: context.params.slug })
.findOne();
And render it:
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
<img :src="post.img" :alt="post.alt" />
<div v-html="$md.render(post.body)"></div>
</div>
<!-- fetch post -->
</template>
<script>
export default {
async asyncData(context) {
const post = await queryContent("/posts")
.where({ slug: context.params.slug })
.findOne();
return {
post,
};
},
};
</script>
We're using the $md
helper provided by content module to render the Markdown into HTML.
This will display the full post content and data on each post page!
About Page
Lastly, let's add some dummy content to the About page at pages/about.vue
:
<template>
<div>
<h1>About My Blog</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras et lectus in
quam convallis cursus a non lacus. Nam rhoncus purus id erat consectetur
euismod.
</p>
</div>
</template>
And that completes all the core pages for our blog!
Recap
In this tutorial, we learned how to:
- Create a Nuxt 3 project from scratch
- Define routes and basic page structure
- Use layouts for consistent header/footer
- Manage assets like CSS and images
- Leverage content module for Markdown content
- Programmatically render posts on homepage and listing
- Display full post content on post page
- Build out other app pages like About
While this covers the fundamentals, here are some ideas to improve the blog further:
- Install Tailwind CSS for styling
- Add pagination to posts list
- Implement search and filters
- Build an admin UI to manage posts
- Deploy to production using Vercel/Netlify
Nuxt 3 provides a fantastic developer experience and allows quickly building fast, scalable apps. I hope you enjoyed learning how to create a blog with Nuxt 3! Let me know if you have any other Nuxt topics you'd like me to cover.