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

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

Easily setup blog website using nuxt 3, nuxt content v2 and tailwindcss.

Fazail Alam
Frontend dev · Thu Jun 30 2022

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

  • Document-Driven Mode

    • Create pages in Markdown in the content/ directory
    • Use Nuxt layouts in your Markdown pages
    • Enjoy meta tag generation from Markdown files
    • Generated navigation based on your pages
  • Switch between Light & Dark mode :moon:

  • Access 100,000 icons from 100+ icon sets with the <Icon> component

  • Highlight code blocks with Shiki

  • Create Vue components and use them in your Markdown

  • Deploy on any Node or Static hosting: GH Pages, Vercel, Netlify, Heroku, etc.

Installation

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

terminal
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
  • router.option.ts: This file will handle scroll position of previous route and smooth scroll to hash urls.
  • components: Here you can define your vue components.
  • components/content: Components from here will be use in the markdown files.
  • content: Here you can write your pages content and it will be assign to route according to the file’s name.
  • layouts: Define layouts of your website. default.vue will used everywhere if no custom layout is set.
  • public: Public folder of your website.
  • app.vue: Main file of your project.
  • tailwind.config.js: Tailwind css configuration file.

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
content/blog/index.md
---
navTitle: Blogs
layout: blogs
---

# Find All blog here

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

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

# Another blog 

This is the second blog :sparkles:
content/blog/third
---
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.

blogs.vue
<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>
blog.vue
<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.

Share on: