How to create blog with nuxt v3, nuxt content v2 and tailwindcss
Fazail Alam Thu Jun 30 2022

How to create blog with nuxt v3, nuxt content v2 and tailwindcss

Nuxt is vue full stack framework. Content is a nuxt module to help you read content/ directory of your project and query the content from .md, .yml, .csv and .json files. Currently Nuxt3 is in candidate release 4 and soon it will be release to stable. Content v2 has already support for Nuxt3. There is already an official stater template called content-wind. We will be using this template to save some time and it is easy to start.

Features of template

Installation

Run this command in your terminal to to install this template.

npx nuxi init my-website -t atinux/content-wind

Directory structure

/
|--- app/
|	  |-- router.option.ts
|--- components/
|	  |-- content/
|		   |-- Alert.vue
|		   |-- Icon.vue
|		   |-- List.vue
|		   |-- MarkdownBlock.vue
|	  |-- ColorModeSwitch.vue
|	  |-- Navbar.vue
|--- content/
|	  |-- 1.index.md
|	  |-- 2.about.md
|--- layouts/
|	  |-- default.vue
|	  |-- full-width.vue
|--- public/
|	  |-- cover.jpg
|	  |-- favicon.ico
|--- app.vue
|--- tailwind.config.js

Writing your first article

Start with 1.index.md file in the content/ directory. This file is your home page of your website /. To define meta data of your pages, you can define it inside the --- section of the markdown file. Since it markdown file you need to markdown syntax. You can also use vue components inside markdown file. For inline component use :name-of-component this syntax and for slot component start with ::name-of-component and end with ::. Below is example of MDC syntax:

// meta data of page
---
title: 'My Page Title'
description: 'What a lovely page.'
image:
  src: '/assets/image.jpg'
  alt: 'An image showcasing My Page.'
  width: 400
  height: 300
head:
  meta:
    - name: 'keywords'
      content: 'nuxt, vue, content'
    - name: 'robots'
      content: 'index, follow'
    - name: 'author'
      content: 'Fazail Alam'
---

// inline component with prop
// component from components/content/Icon.vue
:my-icon{name="ph:check-circle-light"}

// component with slots
::card{class="bg-white rounded-md"}
// title slot
#title
This is a good title.

// default slot
#default
Good content.
::

Creating blogs

Create a folder called blog inside content/ directory. Inside this folder create 3(For this tutorial only, you can as many as you want) blogs and 1 index.md.

content/
  	|---blog/
		|--- 1.first blog.md
		|--- 2.second blog.md
		|--- 3.third blog.md
		|--- index.md
---
navTitle: Blogs
layout: blogs
---

# Find All blog here

List Blog
---
head.title: My First Blog
image: cover.jpg
layout: blog
createdAt: 01-06-22
---

# Hello world
 This is description.
---
head.title: Second Blog
image: cover.jpg
layout: blog
createdAt: 02-06-22
---

# Another blog 

This is the second blog :sparkles:
---
head.title: Third Blog
image: cover.jpg
layout: blog
createdAt: 03-06-22
---

# Third blog 

This is third blog :heart:

Customizing layout

Rename the full-width.vue to blogs.vue and create blog.vue file. In blogs.vue file we will query the list of blog from content/blog/ directory and display it. Below the <slot /> tag add iteration of blog. In blog.vue file we display single post.

<template>
    <div>
		...
		<slot />
		<div v-for="post in posts" :key="post._path">
			<img class="aspect-video object-cover object-center" :src="post.image" :alt="post.title" />
			<NuxtLink :to="post._path">
				<h2>{{ post.title }}</h2>
			</NuxtLink>
			<p>{{ post.description }}</p>
		</div>
    </div>
</template>
<script setup>
// using nuxt useAsyncData composable to query
const { data: posts } = await useAsyncData("posts", () => {
    return queryContent("blog/")
		.where({ _path: { $ne: "/blog" } })  // exclude content/blog/index.md
		.sort({ createdAt: -1 }) // sort by createdAt desc
		.only(['title','image','_path','description']) // only select these fields
        .find();
});
</script>
<template>
	<div
        class="py-10 m-auto bg-white sm:px-8 sm:shadow dark:bg-gray-800 sm:rounded"
    >
        <main
            class="max-w-2xl px-4 mx-auto prose sm:px-8 prose-gray dark:prose-invert"
        >
		// loading image from public directory
		<img :src="`/${page.image}`" :alt="page.title" class="aspect-video" />

		// Post content 
		<slot/>

			<div class="flex justify-between items-center">
				// Prevent showing content/blog/index.md route
				// Previous Post Link 
				<NuxtLink v-if="prev && prev._path != '/blog'" :to="prev._path">
					{{ prev.title }}
				</NuxtLink>
				<div v-else></div>
				// Next Post Link
				<NuxtLink v-if="next" :to="next._path">
					{{ next.title }}
				</NuxtLink>
				<div v-else></div>
			</div>
        </main>
    </div>
</template>
<script setup>
// get page data from useContent composable
const { next, prev, page } = useContent(); 
</script>

Our blog is ready. Now to run the project on development server run npm run dev -- -o. -o flag will automatically open the browser.

Want frameworks news and updates?

Sign up for our newsletter to stay up to date.