Create A Blog With NuxtJS

Create NuxtJS Application

yarn create nuxt-app <project-name>
cd <project-name>
yarn dev

Install Content Package

yarn add @nuxt/content
{ 
modules: [
'@nuxt/content'
],
content: { // Options }
}
--- 
title:
slug:
category:
extension: md
createdAt:
---

Default Layout

<template>
<Nuxt />
</template>
<template>
<div>
<header-bar></header-bar>

<div class="w-full container-md mx-auto px-6">
<Nuxt />
</div>

<footer-bar></footer-bar>
</div>
</template>

Error Layout

<template>
<div class="my-8">
<h1>Page Not Found</h1>
</div>
</template>

<script>
export default {
layout: 'error',
props: {
error: {
type: String,
default: '404',
},
},
}
</script>

Homepage

const articles = await $content('articles')
.sortBy('createdAt', 'desc')
.limit(5)
.fetch()
<template>
<div class="container">
<div class="border-b border-gray-500 pb-2 my-4" v-for="post in articles" :key="post.slug">
<h2 class="text-4xl mb-8 font-bold">
<NuxtLink :to="{ name: 'slug', params: { slug: post.slug.toLowerCase() } }">
{{ post.title }}
</NuxtLink>
</h2>
<nuxt-content :document="{ body: post.excerpt }" />
</div>
</div>
</template>

<script>
export default {
async asyncData({ $content, params }) {
const articles = await $content('articles')
.sortBy('createdAt', 'desc')
.limit(5)
.fetch()

return { articles }
},
}
</script>

Homepage SEO

export default {
// Global page headers (https://go.nuxtjs.dev/config-head)
head: {
title: 'Paulund',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'alternate', type: 'application/rss+xml', href: '/feed/rss.xml' },
],
},
}

Homepage Test

yarn add --dev jest @nuxt/test-utils
module.exports = {
preset: '@nuxt/test-utils'
}
import { get, setupTest } from '@nuxt/test-utils'

describe('Homepage', () => {
setupTest({
server: true,
setupTimeout: 120000,
})

it('renders the index page', async () => {
const { body } = await get('/')

expect(body).toContain('<div class="container">')
})
})

Create A Single Post Page

---
title:
slug:
category:
extension: md
createdAt:
---
<script>
export default {
async asyncData({ $content, params, error }) {
const post = await $content('articles', params.slug)
.fetch()
.catch((e) => {
error({ statusCode: 404, message: 'Page not found' })
})

const postDate = new Date(post?.createdAt)

return { post, postDate }
},

head() {
return {
title: this.post.title,
}
}
}
</script>
<template>
<div>
<div class="flex items-center pt-10 pb-4 mb-4">
<section class="w-full">
<h1 class="mt-4 mb-0 text-5xl">{{ post.title }}</h1>
</section>
</div>

<div class="mb-8">
<nuxt-content :document="post" />
</div>
</div>
</template>
<nuxt-content :document="post" />
---
title:
slug:
category:
extension: md
createdAt:
---
<script>
import titlefy from '~/mixins/titlefy'

export default {
mixins: [titlefy],

async asyncData({ $content, params }) {
const articles = await $content('articles')
.only(['title', 'slug', 'excerpt', 'category'])
.where({ category: params.slug })
.sortBy('createdAt', 'desc')
.fetch()

return { articles }
},

head() {
return {
title: this.titlefy(this.slug),
}
},
}
</script>
<template>
<div>
<div class="bg-gray-200 p-8 mb-8">
<h1 class="text-4xl">{{ titlefy(slug) }}</h1>
</div>

<div class="border-b border-gray-500 pb-2 my-4" v-for="post in articles" :key="post.slug">
<h2 class="text-4xl mb-8 font-bold">
<NuxtLink :to="{ name: 'slug', params: { slug: post.slug.toLowerCase() } }">
{{ post.title }}
</NuxtLink>
</h2>
<nuxt-content :document="{ body: post.excerpt }" />
</div>
</div>
</template>

Create a Search Component

<script>
export default {
data() {
return {
searchTerm: '',
searchResults: [],
}
},

methods: {
async searchPosts() {
if (this.searchTerm.length < 3) {
this.searchResults = []
return
}

// Search fields are defined in the configuration fullTextSearchFields, default ['title', 'description', 'slug', 'text']
this.searchResults = await this.$content('articles')
.search(this.searchTerm)
.limit(5)
.fetch()
},
},
}
</script>
<template>
<div
id="article-search"
class="flex items-center text-center py-12 mb-4 bg-gray-100"
>
<div class="w-full mx-auto px-6 flex-col">
<h1 class="mb-6">Search The Articles</h1>
<div class="mx-1">
<section>
<div>
<input
v-model="searchTerm"
type="search"
placeholder="Search for a article"
autocomplete="off"
@keydown="searchPosts"
/>
</div>
<div class="relative">
<section v-if="searchResults.length">
<NuxtLink
v-for="post in searchResults"
:key="post.slug"
:to="{
name: 'slug',
params: { slug: post.slug.toLowerCase() },
}"
class="w-full text-left bg-white block p-4 hover:bg-gray-100"
>
{{ post.title }}
</NuxtLink>
</section>
</div>
</section>
</div>
</div>
</div>
</template>

Create A Contact Page

npm i nuxt-mail
modules: [
'@nuxt/content',
'@nuxtjs/axios',
['nuxt-mail', {
message: {
to: 'email@example.com',
},
smtp: {
host: "smtp.example.com",
port: 587,
auth: {
user: '',
pass: ''
},
},
}],
],
<template>
<div>
<h1>Contact</h1>

<div v-if="emailSent" class="alert alert-success">
<p>Email sent</p>
</div>

<form v-on:submit.prevent="submitForm" v-if="!emailSent">
<div>
<label>Name</label>
<p><input type="text" id="name" placeholder="Name" required="required" class="input" v-model="name"></p>
</div>

<div>
<label>Email</label>
<p><input type="text" id="email" placeholder="Email" required="required" class="input" v-model="email"></p>
</div>

<div>
<label>Message</label>
<p><textarea id="message" placeholder="Message" required="required" class="input" v-model="message"></textarea></p>
</div>

<div>
<p><button id="send" class="button is-primary">Send</button></p>
</div>
</form>
</div>
</template>

<script>
export default {
data() {
return {
name: '',
email: '',
message: '',
emailSent: false
}
},
methods: {
submitForm () {
this.$mail.send({
from: this.email,
subject: 'Contact form',
text: this.name + '<br>' + this.message,
}).then(() => {
this.emailSent = true
})
}
}
}
</script>

Add Google Analytics

npm install --save-dev @nuxtjs/google-analytics
{
buildModules: [
'@nuxtjs/google-analytics'
],
}
googleAnalytics: { id: 'UA-XXX-X' }

Add RSS Feed

npm install @nuxtjs/feed
feed() {
const baseUrlArticles = 'https://website.com'
const baseLinkFeedArticles = '/feed'
const feedFormats = {
rss: { type: 'rss2', file: 'rss.xml' },
json: { type: 'json1', file: '' },
}
const { $content } = require('@nuxt/content')

const createFeedArticles = async function (feed) {
feed.options = {
title: 'Blog RSS',
description: 'RSS feed',
link: baseUrlArticles,
}
const articles = await $content('articles')
.sortBy('createdAt', 'desc')
.limit(20)
.fetch()

articles.forEach((article) => {
const url = `${baseUrlArticles}/${article.slug}`

const postDate = new Date(article?.createdAt)
feed.addItem({
title: article.title,
id: url,
link: url,
date: postDate,
description: article.description,
content: article.bodyPlainText,
})
})
}

return Object.values(feedFormats).map(({ file, type }) => ({
path: `${baseLinkFeedArticles}/${file}`,
cacheTime: 1000 * 60 * 15,
type,
create: createFeedArticles,
}))
},

Optimize CSS Builds With PurgeCSS

npm install --save-dev nuxt-purgecss
buildModules: [ 'nuxt-purgecss', ],
build: { extractCSS: true, },

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store