Nextjs
Published in

Nextjs

Nextjs + Markdown + Content layer

How to build the static blog with Content layer, markdown, and nextjs?

Build an entire static blog with markdown and contentlayer— full support of Home, Category, tag, search, SEO, sitemap and pagination. #series 2

Create a static blog with a Content layer, markdown, and next.
Create a static blog with a Content layer, markdown, and next.

The content layer is a new javascript content management library. The content layer help organize your markdown file.

Benefits of the content layer

  1. Automatic type definition
  2. Built-in validations
  3. Caching and incremental regeneration
  4. Live reloading
  5. Clean code and better readability

Steps

  1. Project Setup
  2. Project Structure
  3. Posts And Images Dummy data
  4. Pages
  5. Layout
  6. Home Page
  7. Reading Page
  8. Category Page
  9. Tag Page
  10. Search Page and Functionality
  11. SEO
  12. Sitemap
  13. pagination

Note:

My project builds with javascript, and I’m not using typescript in my contentlayer.

Project Setup

The happy news with the contentlayer npm package. With contentlayer, you use fewer third-party packages to start working with markdown files.

npm install -D contentlayer next-contentlayer

Project Structure

The folder structure in contentlayer with nextjs.
The folder structure in contentlayer with nextjs.

Posts And Images Dummy data

I provide dummy blog and image data for your study. the markdown file present in posts folder and images in public/images folder.

Pages

In the pages folder, all pages are present.

  1. pages/index.js Home page
  2. pages/blog/[slug].js reading page for our blog.
  3. pages/category/[slug].js Category page.
  4. pages/tag/[slug].js Tag pages.
  5. pages/Search.js Search pages.

Layout

Define the layout for our blog in pages/_app.js file.

import '../styles/globals.css'import Head from "next/head";import Header from "../components/Header";import Footer from "../components/Footer";function MyApp({ Component, pageProps }) {return ( <><Head><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossOrigin="anonymous" /></Head><Header/><Component {...pageProps} /><Footer /></>)}export default MyApp

Home Page

The first step is to import two packages from contentlayer .

import { allPosts } from "contentlayer/generated";import { pick } from "@contentlayer/client";

Fetch all posts using the getStaticProps in contentlayer/generated .

  1. contentlayer/generated Provide all the posts.
  2. @contentlayer/client Help to filter post data.
// fetch all postsexport async function getStaticProps() {
// all postsconst posts = allPosts.map((post) => pick(post, ["title", "date", "slug", "description", "summary", "draft", "image", "images", "tags", "categories"]));// filter publish postsconst publish= posts.filter( (post, i) =>{ return post.draft===false })return { props: { posts: publish.sort(sortByDate), }, }}

Reading page

The reading component helps to read the full article on your blog. reading page path is pages/blog/[slug].js .

The Import all the posts from contentlayer/generated

import { allPosts } from "contentlayer/generated"

Generate all paths using getStaticPaths nextjs function.

export async function getStaticPaths() {//  filter the post and get the publish post.const posts= allPosts.filter((post) =>{ return post.draft===false })// get all the post slugconst publish = posts.map((post) => ({ params: { slug: post.slug } }))return { paths: publish, fallback: false, }}

Reading the single post using the getStaticPropsand returning the UI component.

export async function getStaticProps({ params: { slug } }) {// fetch a single post by slugconst post = allPosts.find((post) => {     return  post.slug === slug})return { props: { post } }}

Category Page

The category page fetches all posts base on the category name. the category path is pages/category/[slug].js .

The Import all the posts from contentlayer/generated

import { allPosts } from "contentlayer/generated"

Generate all category paths using getStaticPaths nextjs function.

export async function getStaticPaths() {let paths =[]
// get all category paths
allPosts.map( post => { if (post.draft===false){ post.categories.map( category=> { const slug = slugify(category) paths.push({ params: { slug } }) } ) } })return { paths, fallback: false, }}

Fetch all posts base on category slug using the getStaticPropsand returning the UI component.

export async function getStaticProps({ params: { slug } }) {let posts =[]
// get all category posts base on slugconst post= allPosts.map( (post) => { if (post.draft===false){ post.categories.filter( category => { const categorySlug = slugify(category) if(categorySlug === slug ){ posts.push(post) } })return posts } })return { props: { posts} }}

UI component for the category.

export default function Category({ posts }) {return (        <div className="container my-3">             <div className="row">                 <div className="col-lg-10 post-date m-1 p-2m-auto">      {          posts.map((post, index) => {                 return <ItemPost key={index} post={post} />           })     }</div> </div> </div>)}

Tag Page

The tag page fetches all posts base on the tag name. the tag path is pages/tag/[slug].js .

The Import all the posts from contentlayer/generated

import { allPosts } from "contentlayer/generated"

Generate all tag paths using getStaticPaths nextjs function.

export async function getStaticPaths() {let paths =[]// get all tag paths allPosts.map(       post => {          if (post.draft===false){                 post.tags.map(                      tag=> {                           const  slug = slugify(tag)                           paths.push({ params: { slug } })                   }               )          }     })return { paths, fallback: false, }}

Fetch all posts base on tag slug using the getStaticPropsand returning the UI component.

export async function getStaticProps({ params: { slug } }) {let posts =[]// get all tag posts base on slugconst post= allPosts.map(   (post) => {       if (post.draft===false){              post.tags.filter(                   tag => {                          const  tagSlug = slugify(tag)                          if(tagSlug === slug ){                              posts.push(post)                           }      })return posts}})return { props: { posts} }}

UI component

export default function Category({ posts }) {return (<div className="container my-3"><div className="row"><div className="col-lg-10 post-date m-1 p-2m-auto">{posts.map((post, index) => {return <ItemPost key={index} post={post} />})}</div> </div> </div>)}

Search Page and Functionality

We add search functionality to the static blogs. With my step, you add successfully add search functionality to your blog.

Steps

  1. Create Search.json file
  2. Using Search.json file

Create Search.json file

Create search.json file using all post markdown data in your blog root level.

// serach functionaltyconst jsonString = JSON.stringify(publish)fs.writeFileSync('./search.json', jsonString, err => {  if (err) {     console.log('Error writing file', err)   } else {     console.log('Successfully wrote file')  }})

I’m create serach.json file on pages/index.js

// fetch all postsexport async function getStaticProps() {const posts = allPosts.map((post) => pick(post, ["title", "date", "slug", "description", "summary", "draft", "image", "images", "tags", "categories"]));// filter publish postsconst publish= posts.filter(       (post, i) =>{            return post.draft===false       })/*// serach functionalty// convert data into string*/const jsonString = JSON.stringify(publish)
// create serach.json file in root levelfs.writeFileSync('./search.json', jsonString, err => { if (err) { console.log('Error writing file', err) } else { console.log('Successfully wrote file') }}) return { props: { posts: publish.sort(sortByDate),},}}

Using Search.json file

We use the search.json file on pages/serach.json the page.

export default function Search() {const { query } = useRouter()const TempPosts = [] search.map(        (post) => {            if (post.draft === false) {            if (post.title.toLowerCase().includes(query.q) ||     post.summary.toLowerCase().includes(query.q) || post.description.toLowerCase().includes(query.q)) {                TempPosts.push(post)           } else {             TempPosts.push(null)  }}})//   remove null in postsconst posts = TempPosts.filter(      path => {        return path && path    })return (<div>   <Banner />    <div className="container">       <div className="row">          <div className="col-lg-8 m-auto">              {                posts.length > 0 ? posts.map((post, index) => (                   <Post key={index} post={post} />                     )) : <div className='m-auto p-5 mx-5 '>                  <h2 className='text-center'>                  {   
query.q ? `No post find base on ${query.q}` : 'loadding.. '

}
</h2>
</div>}</div> </div></div> </div>)}

Conclusion

We create a static site blog for the Home, Category, Tag, and Search components in the article.

We create an SEO, sitemap, and pagination for the static blog in the next article.

Any feedback and feature requests. plz, tell me in the comment section.

Thanks

You can follow and read more articles on officialrajdeepsingh.dev and tag them on Twitter.

Read More content on the Nextjs. Sign up for a free newsletter and join the frontend web community on medium.

--

--

--

Nextjs Publication is an unofficial publication of Nextjs. Our goal is to bring beginners and pro developer in one place, and everybody understands the basic concept of next.js.

Recommended from Medium

ZEGOCLOUD: Major product ZEGO Call Gets Released!

Welcome to my blog!

Intermediate JavaScript: What’s the Difference Between Primitive Values and Object References?

Taking Hash Tables Off The Shelf

How to notify your Angular Application using SignalR, and ASP.Net Core API

Negative words in Japanese are formed by conjugation.

Insertion Sort

Fun with React Hooks

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
Rajdeep singh

Rajdeep singh

JavaScript || Reactjs || Nextjs || Python || Rust || Biotechnology || Bioinformatic || Front-end Developer || Writer || https://officialrajdeepsingh.dev/

More from Medium

Build the static blog with Next.js and Markdown

Migrating To NextJS From React

GET and POST with Next.js SWR and Supabase Auth

Build an extendable CV profile with Next.js and Vercel