visit
npx create-nuxt-app blog-for-developers
The command above will kick off a command-line interface (CLI) prompt that will allow us to configure various aspects of our project.Now that the boilerplate is set up with all the files and folders, let’s run the development server which is accessible on
//localhost:3000
inside the project.# change directory to project folder
cd blog-for-developers
# start the development environment
npm run dev
What is Nuxt?
NuxtJS is an open-source intuitive Vue framework that allows us to build user interfaces, making web development simple and powerful.
What is Nuxt Content?
During the initial setup of this project, we selected the Nuxt content module. This module lets us use our codebase as a Git-based headless CMS to access markdown, JSON, YAML, XML, and even CSV files through an API-like interface. With this module
@nuxt/content
, it will inject the $content
instance globally in our project to access it anywhere in a project. What is Cloudinary?
Cloudinary is a cloud-service image and video management tool for web and mobile applications. With Cloudinary, we can upload, create, and manage digital experiences across any browser and device.
Before we create the content for the blog in markdown using Nuxt content, let’s first create the landing page for the blog that will include the navigation links and some text.
Add Font Style
In the
nuxt.config.js file
, we can add our favorite font to the head section and use a particular font family as seen below:The above code block will have no effect on the page until we write the CSS that will use the font.
Now, add the following code to the
pages/index.vue
which will include the navigation links and some text on the home page. Remove the component, <Tutorial />
.<template>
<section class="showcase">
<header>
<h2 class="logo">
<nuxt-link to="/">ecosurf</nuxt-link>
</h2>
<nav class="nav desktop">
<ul>
<li class="nav__list">
<nuxt-link to="/blogs">Stories</nuxt-link>
</li>
</ul>
</nav>
</header>
<video
autoplay
loop muted
src="//res.cloudinary.com/terieyenike/video/upload/v1651393236/mixkit-tropical-island-landscape-view-4692-large_yanvml.mp4"></video>
<div class="overlay"></div>
<div class="text">
<h2 data-type="uppercase" class="stroke">Never Stop</h2>
<h3 data-type="uppercase">Exploring The World</h3>
<p>
View of the tropical island landscape, from a hill with houses, palm
trees and many trees, and in the distance the hills that surround the
sea, on a sunny day.
</p>
</div>
<ul class="social">
<li>
<a href="//twitter.com/terieyenike" rel="noopener noreferrer" target="_blank"><img
alt="twitter profile" src="//i.ibb.co/Wnxq2Nq/twitter.png"/></a>
</li>
<li>
<a href="//instagram.com/terieyenike" rel="noopener noreferrer" target="_blank"><img
alt="instagram profile" src="//i.ibb.co/ySwtH4B/instagram.png"/></a>
</li>
</ul>
</section>
</template>
In the above code block, we included a
<video>
with the source of the video stored in Cloudinary with attributes such as autoplay, loop, and muted that mute the video when it loads. To obtain the media stored in Cloudinary, we upload our desired video to the media library tab. Thereafter, copy the URL which will be attached to the src
of the <video>
.Also, we referenced the links in the navigation header with the
nuxt-link
component to both the home /
and the /blogs
routes which we will create to view all our blogs.assets/styles/main.css
, and include the following from this .nuxt.config.js
file, update the file to include the created CSS file with the following:export default {
...
css: ['@/assets/styles/main.css'],
...
}
To build our blog site, we need to have content to display and render on the
/blogs
page. First, create a content
directory and then the blog directory that will contain all the markdown files. Writing all the content in markdown supports a lot of the markdown syntax standards such as the headings, links, and code blocks with syntax highlighting for different programming languages.content/blogs/egghead.md
.---
title: Egghead
cover_image: //res.cloudinary.com/terieyenike/image/upload/v1651446130/pexels-jeremy-bishop-8241100_oklfpe.jpg
author: Teri Eyenike
description: All we need to do is open up our terminal and run the command npm install @nuxt/content. Once it's installed, you'll see that inside of our package.json, we see our Nuxt Content module. Next, to finalize the setup, let's go ahead and open up our nuxt.config.js, and let's go ahead and scroll down to the section that's labeled Modules.
date: May 2, 2021
publishOn: 2021-05-02T00:00:00
tags: ["learning", "platform"]
---
[Egghead](//www.egghead.io) is a great platform for developers to enhance their skills!
<!-- more content -->
Fetching the Posts
To fetch the posts for the blog page, let’s create a new route under
pages
. Create a folder, called blogs, and in there create index.vue
file for the directory, blogs. pages/blogs/index.vue
file to display the rendered markdown files.// pages/blogs/index.vue
<template>
<main>
<header>
<h2 class="logo">
<nuxt-link to="/">ecosurf</nuxt-link>
</h2>
<nav class="nav desktop">
<ul>
<li class="nav__list">
<nuxt-link to="/blogs">Stories</nuxt-link>
</li>
</ul>
</nav>
</header>
<div class="container section">
<div class="container__grid">
<div v-for="blog in blogs" :key="blog.slug + blog.createdAt" class="card">
<img :src="blog.cover_image"
alt="blog photographs"/>
<div class="pad__card">
<div class="author">
<p class="author__name">{{ blog.author }}</p> <span>|</span>
<p>{{ blog.date }}</p>
</div>
<h2 class="title">{{ blog.title }}</h2>
<p>{{
blog.description.substring(0, 150)
}}...</p>
<button>
<nuxt-link :to="`/blogs/${blog.slug}`">Read More</nuxt-link>
</button>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
async asyncData({$content}) {
const blogs = await $content("blogs").sortBy("publishOn", "desc").fetch()
return {
blogs
}
},
head() {
return {
title: "Read interesting stories as a nomad",
meta: [
{
hid: 'description',
name: 'description',
content: 'Daily and juicy content as you learn, work, and relax. WFH'
}
]
}
}
}
</script>
<style scoped>
header {
background: #111111;
position: unset;
}
.container {
width: 85%;
max-width: 75rem;
margin-inline: auto;
}
.section {
padding: 3em 0;
}
.container__grid {
display: grid;
gap: 2em;
grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}
.card {
background: #f0f7f4;
box-shadow: 0px 4px 3px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
.card img {
object-fit: cover;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
width: 100%
}
.pad__card {
padding: 2em;
}
.author {
display: flex;
align-items: center;
}
.author, .title {
margin-bottom: 1em;
}
.author span {
margin: 0 0.3em;
}
.author__name {
text-transform: capitalize;
}
button {
border: unset;
padding: 1em 2em;
margin-top: 2em;
background: #0D5159;
font-weight: 700;
cursor: pointer;
}
button a {
color: #fff;
}
</style>
asyncData
: used to generate our page from Nuxt as it generates individual files statically to users. We pass in the $content
method where the blog notes are stored, the sortBy()
function, and the fetch
method. sortBy()
: takes two parameters, publishOn
(sort it by date) and the desc attributes on how you want the content to appear based on the most recent content published/blogs/
the blog slug on the Read more button using the <nuxt-link>
component.Rendering the posts
The last step for viewing the snippet of the rendered posts from the content directory is to use the v-for directive and loop over the blogs and render each article in a card.
Now let’s create pages for each blog post to have dynamic URL routes. In Nuxt, to create dynamic pages, we append an underscore before the .vue file name so as not to make the URL hardcoded.
We then create the
_slug.vue
file under the blogs directory and add the following code:// pages/blogs/_slug.vue
<template>
<main>
<header>
<h2 class="logo">
<nuxt-link to="/">ecosurf</nuxt-link>
</h2>
<nav class="nav desktop">
<ul>
<li class="nav__list">
<nuxt-link to="/blogs">Stories</nuxt-link>
</li>
</ul>
</nav>
</header>
<div class="container">
<div class="return">
<nuxt-link to="/">
<img alt="back to home" src="/home.png"/>
</nuxt-link>
<span>/</span>
<nuxt-link to="/blogs">Blog</nuxt-link>
<span>/</span>
<p>{{ note.title.substring(0, 15) }}...</p>
</div>
<section>
<h1>{{ note.title }}</h1>
<nuxt-content :document="note" cla />
</section>
</div>
</main>
</template>
<script>
export default {
async asyncData({$content, route}) {
const note = await $content(`blogs/${route.params.slug}`).fetch()
return {
note
}
}
}
</script>
<style scoped>
header {
background: #111111;
position: unset;
}
.container {
width: 85%;
max-width: 75rem;
margin-inline: auto;
}
section {
padding-bottom: 2em;
}
.return {
display: flex;
margin: 1.5em 0;
}
.return img {
width: 20px;
height: 20px;
}
.return span {
margin: 0 1em;
}
h1 {
margin-bottom: 1em
}
</style>
${route.params.slug}
: We use the content to fetch a specific file from the directory blogs using the route parameter and the slug which will map to our actual file namedocument
with the entire note
objectscoped
: Defining the scoped to the style element make sure that the styling only applies to a specific pageLearn More