Nextjs + Markdown + Content layer
How to build the static blog with a Content layer, markdown, and nextjs?
Build an entire static blog with markdown and content layer— full support of Home, Category, tag, search, SEO, sitemap and pagination. #series 2
The content layer is a new javascript content management library. The content layer help organize your markdown file.
Benefits of the content layer
- Automatic type definition
- Built-in validations
- Caching and incremental regeneration
- Live reloading
- Clean code and better readability
Read more about content layer
Steps
- Project Setup
- Project Structure
- Posts And Images Dummy data
- Pages
- Layout
- Home Page
- Reading Page
- Category Page
- Tag Page
- Search Page and Functionality
- SEO
- Sitemap
- pagination
Note:
My project builds with javascript, and I’m not using typescript in my content layer.
Project Setup
The happy news with the content layer npm package. With the content layer, you use fewer third-party packages to start working with markdown files.
npm install -D contentlayer next-contentlayer
Project Structure
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.
pages/index.js
Home pagepages/blog/[slug].js
reading page for our blog.pages/category/[slug].js
Category page.pages/tag/[slug].js
Tag pages.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
.
contentlayer/generated
Provide all the posts.@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
.
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 getStaticProps
and 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
.
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 pathsallPosts.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 getStaticProps
and 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
.
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 getStaticProps
and 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
- Create
Search.json
file - 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.