DEV Community

Cover image for Create minimalist blog with Nuxt 3 and Tailwind CSS
Tomasz Kikowski
Tomasz Kikowski

Posted on

Create minimalist blog with Nuxt 3 and Tailwind CSS

Nuxt 3 is one of the hottest tools in web development right now, and for good reason. This powerful tool makes it easy to create any web application you wish. In this guide, I will show you how to create a minimalistic blog with Nuxt 3 as a base, Tailwind CSS for styling user interface, and Markdown to create posts. Let's dive in and create a stunning blog together!

Prerequisites

You have to install the following dependencies at the beginning:

Creating a new Nuxt 3 application

You need to start this journey by creating a new Nuxt 3 project. To do this run the following commands in your terminal:

npx nuxi@latest init nuxt3-blog-app
cd nuxt3-blog-app
npm install
Enter fullscreen mode Exit fullscreen mode

Side note: If you are not familiar with NPX you can jump to this article about the differences between NPX and NPM.

By running the above command you will create Nuxt 3 application in nuxt3-blog-app directory, then you will go to this directory and install required dependencies. After this, you can run this project by:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Installing Tailwind CSS

The next step you should do is to install Tailwind CSS together with Post CSS and Autoprefixer. After successful installation, run the init command to generate tailwind.config.js file.

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init
Enter fullscreen mode Exit fullscreen mode

In the generated configuration file you need to specify paths to all components, and any other source files that will contain Tailwind class names.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./components/**/*.{js,vue,ts}",
    "./layouts/**/*.vue",
    "./pages/**/*.vue",
    "./plugins/**/*.{js,ts}",
    "./nuxt.config.{js,ts}",
    "./app.vue",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

Then you should create the main CSS file (for example /assets/css/main.css) for all Tailwind CSS rules with the following content:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Finally you have to add PostCSS configuration and include the newly created CSS file in nuxt.config.js file:

export default defineNuxtConfig({
  css: ['~/assets/css/main.css'],
  postcss: {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

Installing @nuxt/content module

The @nuxt/content module (https://content.nuxtjs.org) is a great utility that helps you to manage content on your blog. You can use following command to install this module:

npm install -D @nuxt/content
Enter fullscreen mode Exit fullscreen mode

Then you need to enable @nuxt/content module by adding it to nuxt.config.js configuration file:

export default defineNuxtConfig({
  modules: ['@nuxt/content'],
  ...
})
Enter fullscreen mode Exit fullscreen mode

Creating and displaying content

To store posts on the blog you can use Markdown files. To do this create a separate directory to store them (for example /content) and add some Markdown files with content and metadata. Add minimum four different blog posts for best look of the blog.

mkdir content
touch first-example-blog-post.md
Enter fullscreen mode Exit fullscreen mode

Here is an example structure of such a blog post:

---
title: "Lorem ipsum dolor sit amet"
author: John Doe
avatar: https://i.pravatar.cc/40
date: Mar 25, 2023
image: https://picsum.photos/1200/520
excerpt: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi at mi suscipit sapien volutpat volutpat. Nam tempor malesuada ligula, et pretium ante ultricies sed.
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
Morbi at mi suscipit sapien volutpat volutpat. Nam tempor malesuada ligula, et pretium ante ultricies sed. 

Cras condimentum ex a facilisis venenatis. Suspendisse congue fringilla risus. Nullam faucibus tincidunt sagittis. Proin vitae aliquet lacus, ac elementum mauris.
Enter fullscreen mode Exit fullscreen mode

Creating pages

You need two types of pages for blog - main blog page for displaying list of all post and single post page. To do this, create index.vue and [[slug]].vue files in /pages. directory and fill them by some dummy template:

<template>
  <div>Blog page</div>
</template>
Enter fullscreen mode Exit fullscreen mode

Next, edit app.vue file in the main directory and change it by adding NuxtPage component (thanks to this Nuxt will read pages inside /pages directory and create routes form them) and some Tailwind CSS classes:

<template>
  <main class="max-w-screen-xl mx-auto px-4 sm:px-6 xl:px-10 py-5 xl:py-10 text-base">
    <NuxtPage />
  </main>
</template>
Enter fullscreen mode Exit fullscreen mode

Displaying content on the main blog page

To display content on main blog page, you need to create some post tile component. Go to /components directory, make a new file - PostTile.vue with following content:

<template>
  <div 
    class="pb-8 lg:pb-12"
    :class="{ 'w-full md:flex': isBig, 'md:w-1/3': !isBig }"
  >
    <div
      class="px-4 lg:px-6"
      :class="{ 'md:w-2/3': isBig }"
    >
      <a :href="post._path" :title="post.title">
        <img :src="post.image" :alt="post.title" class="rounded block">
      </a>
    </div>
    <div
      class="px-4 lg:px-6"
      :class="{ 'md:w-1/3': isBig }"
    >
      <h3 class="my-3" :class="{ 'md:mt-0 md:mb-6': isBig }">
        <a :href="post._path" :title="post.title">{{ post.title }}</a>
      </h3>
      <div class="font-serif text-lg">
        <p
          class="my-3"
          :class="{ 'md:my-6': isBig }"
        >
          {{ post.excerpt }}
        </p>
      </div>
      <div class="flex items-center">
        <img :src="post.avatar" :alt="post.author" class="w-12 h-12 rounded-full">
        <div class="ml-4">
          <strong class="block">{{ post.author }}</strong>
          <span>{{ post.date }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  name: 'PostTile',
  props: {
    post: {
      type: Object,
      default: () => {}
    },
    // First blog post will be displayed as big tile
    isBig: {
      type: Boolean,
      default: false
    }
  },
})
</script>
Enter fullscreen mode Exit fullscreen mode

Next open pages/index.vue file, get all posts and display them using created PostTile.vue component (Nuxt 3 automatically import all components in /components directory so you can just use it in the template):

<template>
  <div class="flex flex-wrap -mx-4 lg:-mx-6">
    <PostTile
      v-for="(post, key) in posts"
      :key="post.title"
      :post="post"
      :is-big="!key"
    />
  </div>
</template>

<script>
import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  async setup() {
    const { data:posts } = await useAsyncData('posts', () => queryContent('/').find())

    return {
      posts
    }
  },
})
</script>
Enter fullscreen mode Exit fullscreen mode

Displaying content on the single post page

The @nuxt/content module will automatically detect Markdown files and create routes based on the file name. The only one thing you need to do is edit /pages/[[slug]].vue file and create appropriate template to display post content:

<template>
  <article>
    <h1 class="my-6 text-center font-serif">
      {{ post.title }}
    </h1>
    <div class="my-8 text-center font-serif text-base text-gray">
      <time>{{ post.date }}</time> &mdash; {{ post.author }}
    </div>
    <img
      :src="post.image"
      :alt="post.title"
      class="rounded block my-10"
    />
    <div class="max-w-3xl mx-auto my-8">
      <ContentRenderer 
        class="content font-serif text-lg"
        :value="post" 
      />
      <div class="sm:flex justify-between items-center mt-16">
        <div class="flex items-center">
          <img :src="post.avatar" :alt="post.author" class="w-12 h-12 rounded-full">
          <div class="ml-4">
            <strong class="block">{{ post.author }}</strong>
            <span>{{ post.date }}</span>
          </div>
        </div>
      </div>
    </div>
  </article>
</template>

<script>
import { defineComponent } from '@vue/composition-api';
import { useRoute } from "vue-router";

export default defineComponent({
  async setup() {
    const route = useRoute()
    const { data:post } = await useAsyncData('post', () => queryContent(route.path).findOne())

    return {
      post
    }
  },
})
</script>
Enter fullscreen mode Exit fullscreen mode

In the above code you may notice ContentRenderer component. This component is built into @nuxt/content module and you can use it to convert Markdown syntax to HTML code.

Styling and adding header

Finally, you need to little improve CSS styles to make this blog look better.

In the nuxt.config.js file add configuration of links to use Google Fonts (in this case Lato & PT Sans):

export default defineNuxtConfig({
  app: {
    head: {
      link: [
        { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
        { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: true },
        { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=PT+Serif:wght@400;700&display=swap' }
      ]
    },
  },
  ...
})
Enter fullscreen mode Exit fullscreen mode

Next add some additional styles in Tailwind CSS configuration:

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    fontFamily: {
      'sans': ['Lato', 'sans-serif'],
      'serif': ['PT Serif', 'serif']
    },
    extend: {
      colors: {
        black: '#393939',
      }
    }
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

And add some additional styles in /assets/main.css file:

@tailwind base;

@layer base {
  h1 {
    @apply text-4xl font-bold;
  }
  h2 {
    @apply text-2xl font-bold;
  }
  h3 {
    @apply text-xl font-bold;
  }
  .content h1, 
  .content h2, 
  .content h3, 
  .content p, 
  .content img {
    @apply my-6;
  }
  .content ul {
    @apply my-6 pl-5 list-disc;
  }
  .content ol {
    @apply my-6 pl-5 list-decimal;
  }
  li {
    @apply my-4;
  }
  @media (min-width: 768px) {
    h1 {
      @apply text-6xl font-bold;
    }
    h2 {
      @apply text-4xl font-bold;
    }
  }
}

@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Tailwind by default normalize all styles, for example by removing default font sizes of headings or margins of paragraphs, lists, etc.

Adding header

To allow navigate between single post and main blog page add header with link to the home page. Create a new file for header component - /components/Header.vue and fill it with the following content:

<template>
  <div  class="text-center uppercase text-3xl py-6 mb-2 border-b border-gray-200">
    <NuxtLink to="/">
      <strong>Nuxt3</strong>Blog
    </NuxtLink>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Include header in app.vue:

<template>
  <Header />
  ...
</template>
Enter fullscreen mode Exit fullscreen mode

Thats all. You've just created simple blog application with Nuxt 3 and Tailwind CSS with content stored in Markdown files.

You can find above code in GitHub repository or check live version on Vercel.

Top comments (0)