nuxt3tailwind

How to build a static blog part 9

Sunday, 9 July 2023


The blog and content

We're using Nuxt Content here, a really nice package that enables us to create and render static blog pages written in markdown, with not much configuration.

First, create a folder in your pages directory called blog. In there make a new file called index.vue. This will be the page which displays a list of our blog posts.

<script setup lang="ts">
import { Article } from '~/types/types'
useHead({
    title: 'Recent Blog Posts',
    meta: [
        { name: 'description', content: 'Index of recent blog posts by me' },
    ],
})

// this returns content typed as 'ParsedContent'
const parsedContent = await queryContent('blog')
    .sort({ date: -1 })
    .only(['_path', 'title', 'description', 'tags'])
    .find()

// This converts it to an Article[] type for our component
const articles = computed(() => {
    return parsedContent as Article[]
})
</script>
<template>
    <section>
        <h1>Recent Posts</h1>
        <hr />
        <BlogPostsList
            v-if="articles.length > 0"
            :articles="articles"
        ></BlogPostsList>
        <p v-else>Sorry, nothing found.</p>
    </section>
</template>

Notice the way we’re getting the posts using a query. This is a feature of the Nuxt Content package. More in the docs. Notice as well I've added a sprinkle of Typescript here. I think it is worth using Typescript when we are dealing with data that we are loading from elsewhere. Ignore any Typescript errors in this component, we'll get to them in a mo.

Then we need the component we're referencing above, so in the components directory, create BlogPostsList.vue.

<script lang="ts" setup>
import { Article } from '~/types/types'

defineProps({
    articles: {
        type: Array<Article>,
        default: () => {
            return []
        },
    },
})
</script>

<template>
    <nav>
        <ul class="list-none pl-0">
            <li
                v-for="{ title, _path, description, tags } in articles"
                :key="_path"
            >
                <NuxtLink :to="_path" class="transition">
                    {{ title }}
                </NuxtLink>
                <br />
                <small>{{ description }}</small>
                <br />
                <BlogTag v-for="tag in tags" :key="tag" :text="tag" />
                <br />
            </li>
        </ul>
    </nav>
</template>

Lets create the types file to keep our friend Typescript happy... Here ya go, create a types/types.ts file:

export interface Article {
    title: string
    _path: string
    description: string
    tags?: string[]
}

There is also a little component to render the tags for our blog posts, so in the components directory, create a sub -directory called content. Components placed in this folder can be used in our markdown files. Create a new file called BlogTag.vue

<template>
    <NuxtLink
        :to="`/blog/tag/${text}`"
        class="bg-slate-300 dark:bg-slate-700 rounded-full px-4 py-1 no-underline mr-2 text-sm transition"
        >{{ text }}</NuxtLink
    >
</template>

<script setup>
defineProps({
    text: { type: String, default: '' },
})
</script>

Now we need the page which actually shows a blog post. In pages make a file and call it [...slug].vue This tells Nuxt that we want our Blog posts to have dynamic slugs and be available at a URL like this: https://my-super-blog/blog/the-blog-slug .

<script setup>
const options = {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
}
</script>


<template>
    <div>
        <article>
            <ContentDoc>
                <template #not-found >
                    <h1>Document not found</h1>
                </template>
                <template #default="{ doc }">
                    <BlogTag
                    v-for="tag in doc.tags"
                    :key="tag"
                    :text="tag"
                    ></BlogTag>
                    <p>
                        <h1>{{ doc.title }}</h1>
                        <small>{{new Date(doc.date).toLocaleDateString('en-GB', options)}}</small>
                    </p>
                    <hr />
                    <ContentRenderer :value="doc" />
                </template>
            </ContentDoc>
        </article>
        <hr />
        <BackButton to="/blog"></BackButton>
    </div>
</template>

Let’s add a link to our blog page in the Navbar. In SiteHeader.vue, add another entry in the links array:

{ name: 'blog', to: '/blog', },

Now all that's left to do is add some content and our blog is ready. Create a file called test-post.md in a folder called blog, inside the content folder, and add some front matter (the meta data about the post), then some text to render.

---
title: "Test post"
description: "this is the description of the test"
date: "2023-01-08" #YYYY-MM-DD
draft: false
tags: ["nuxt3", "test"]
---
Lorem ipsum etc...Put your post here

All being well you should be able to see the test post show up on the blog index page, and be able to click the link to read the post. Future posts can be added the same way, and they will show up in the index page. This blog does not feature any pagination, or search functionality. I suppose that wouldn't take much effort to add, but I don't intend to write enough posts to warrant all that!

Next post →


go back