Lee Froese

Web Developer

Building This Site Using Nuxt

I've been wanting to re-build my website for the past couple of months. My previous website a simple landing page with some links to freelance client websites and my day job. As I've dialed back my freelancing, the site no longer suited my needs. Instead, I wanted to start writing and sharing some of my experiences as a web developer.

Deciding on Nuxt

I knew I wanted a simple markdown blog using something I never had done before. I've used Jekyll in the past and liked it. I'd recently built a new site for the agency I work at using Next.js. I thought, "why not try Vue's framework called Nuxt.js".

Getting Started

Being completely new to Vue and Nuxt, I did some Googling and followed these articles for the most part with a few differences:

With NodeJS already installed, I started by running npx create-nuxt-app blog-name.

You will be presented with a number of configuration options. Here are the choices I made:

  • Project name: Lee Froese 2022
  • Programming language: JavaScript
  • Package manager: NPM
  • UI framework: Tailwind
  • Nuxt.js modules: Content - Git-based headless CMS
  • Linting tools: Prettier
  • Testing framework: None
  • Rendering mode: Universal (SSR / SSG)
  • Deployment target: Static (Jamstack Hosting)
  • Continuous integration: None
  • Version control system: Git

Once installation is finished, cd into the project directory and run npm run dev. This will load a local development server in which you can view your website at localhost:3000.

Tailwind Setup

I found some additional configuration was necessary after selecting Tailwind as my UI framework during the project installation. I followed the guide from Tailwind on Nuxt installation. I had to run the following to install PostCSS and AutoPrefixr: npm install -D postcss@latest autoprefixer@latest @nuxt/postcss8

Next, I created the tailwind.config.js for my project by running npx tailwindcss init. Then, you're required to enable the @nuxt/postcss8 plugin in the nuxt.config.js file.

// nuxt.config.js
export default {
  buildModules: [
    '@nuxt/postcss8',
  ],
}

The PostCSS object including tailwindcss and autoprefixer then needs to be added to the build configuration in the nuxt.config.js file.

// nuxt.config.js
export default {
  build: {
    postcss: {
      plugins: {
        tailwindcss: {},
        autoprefixer: {},
      },
    },
  }
}

Next, within the tailwind.config.js, the template paths for the project need to be added.

// tailwind.config.js
module.exports = {
  content: [
    "./components/**/*.{js,vue,ts}",
    "./layouts/**/*.vue",
    "./pages/**/*.vue",
    "./plugins/**/*.{js,ts}",
    "./nuxt.config.{js,ts}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Create the folder structure assets/css/ in the root of your project, and then create a main.css file that lives inside the css folder. Add the Tailwind directives to the file. You can also add any global styles here.

// assets/css/main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

Add your CSS file to the project by updating the nuxt.config.js file with the css object.

// nuxt.config.js
export default {
  css: [
    '@/assets/css/main.css',
  ],
}

Lastly, I customized the Tailwind theme in the tailwind.config.js file to include my colors and fonts.

// tailwind.config.js
module.exports = {
  theme: {
    colors: {
      'green': '#00b28d',
      'green-dark': '#009072',
      'gray': '#555351',
      'gray-dark': '#323130',
      'gray-light': '#807d7b',
      'white': '#f0f1f7',
      'black': '#000000'
    },
    fontFamily: {
      sans: ['IBM Plex Sans', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Open Sans', 'Helvetica Neue', 'sans-serif']
    },
    extend: {},
  }
}

Blog Setup

As I decided on using @nuxt/content to fetch markdown files, I created the content/ directory and a markdown file in my application next. Here's what my markdown blog posts look like currently.

---
title: Blog Post
description: 'Blog post description content.'
tags:
  - Tag 1
  - Tag 2
---

# Blog Post

Text content...

Displaying Blog Posts

There are two ways in which I want to display blog posts on my site, the first is with a listing of blog posts excerpts on my home page, and the second is the individual blog posts.

Listing Posts on the Home Page

In the pages/ folder, the index.vue file is your home page file. To fetch the blog post data, we can use asyncData which will grab data before the page has been rendered. We can also use the global $content variable that is injected by the @nuxt/content package. Then, we can define what content or fields we want to get for each posts using the .only() method. Lastly, we want to sort the posts by the most recent using the .sortBy() method.

export default {
  name: 'IndexPage',
  async asyncData({ $content }) {
    const posts = await $content()
      .only(['title', 'description', 'tags', 'slug', 'createdAt'])
      .sortBy('createdAt', 'desc')
      .fetch()
    return {
      posts,
    }
  },
}

Next, I created a component called PostPreview and inserted it into my index.vue file and looped over my fetched posts using v-for.

<PostPreview v-for="post in posts" :key="post.slug" :post="post"></PostPreview>

My PostPreview component makes use of the built in NuxtLink component to link to each blog post. It also contains a PostDate component that formats dates into a more readable format. Here is my PostPreview component.

<template>
  <li class="[ mt-5 ]">
    {{index}}
    <NuxtLink class="post-link" :to="`/blog/${post.slug}`">
      <PostDate :date="post.createdAt" />
      <h3 class="[ text-green-dark mb-1 ]">{{ post.title }}</h3>
      <p>{{ post.description }}</p>
      <ul v-if="post.tags" class="[ flex space-x-2 ][ mt-2 ][ text-xs text-gray-light ]">
        <li v-for="tag in post.tags" :key="tag">
          {{ tag }}
        </li>
      </ul>
    </NuxtLink>
  </li>
</template>


<script>
export default {
  props: {
    post: Object,
    index: Number
  }
}
</script>

And here is my PostDate component that makes use of Intl.DateTimeFormt() for formatting the date.

<template>
    <time class="[ block text-xs text-gray-light mb-2 ]">{{ formatDate(date) }}</time>
</template>


<script>
export default {
  props: {
    date: String
  },
  methods: {
      formatDate(dateString) {
          const date = new Date(dateString);
              // Then specify how you want your dates to be formatted
          return new Intl.DateTimeFormat('default', {dateStyle: 'long'}).format(date);
      }
  }
}
</script>

Individual Blog Posts

To display the individual blog posts on a page, we can use dynamic pages in Nuxt. To get started, create the pages/blog/ folder and add a file called _slug.vue in the folder. Again, we can use asyncData and the global $content variable to fetch the blog post. This time, we will also use the params.slug variable to tell the application which blog post to fetch based on the URL slug.

export default {
  async asyncData({ $content, params }) {
    const post = await $content(params.slug).fetch()
    return { post }
  },
}

The HTML markup for my blog posts is fairly simple and uses my PostDate component to format the date again. I'm also using using the nuxt-content component to display the body content of my posts.

<template>
  <main>
    <TheHeader />
    <section class="[ container mx-auto ][ max-w-3xl ][ px-6 ]">
      <PostDate :date="post.createdAt" />
      <nuxt-content :document="post" />
    </section>
  </main>
</template>

Hope this helps or is useful!