Hello every one i decided to add my blogs to my website . Here is how I built it using Next.js and contentlayer
Previously I used to write blogs on Hashnode (as if I have written many blog 😝). So previously I added the blogs link here.
But one day I wanted dont know why that my blogs should be on my website,which is also good for seo.
So here we are if you are reading this then surely I have intergrated contentlayer to my website.
tech stack which I am going to use
next.js
contentlayer
tailwind
terminal npx create-next-app@latest
now add contentlayer to the project
terminal npm add contentlayer next-contentlayer
here come you first error ❌
don't error is quite self explainatory
terminal npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: mdx-example@0.1.0
npm ERR! Found: next@14.2.4
npm ERR! node_modules/next
npm ERR! next@"14.2.4" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer next@"^12 || ^13" from next-contentlayer@0.3.4
npm ERR! node_modules/next-contentlayer
npm ERR! next-contentlayer@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR!
npm ERR! For a full report see:
npm ERR! C:\Users\faisa\AppData\Local\npm-cache\_logs\2024-06-17T06_22_43_221Z-eresolve-report.txt
npm ERR! A complete log of this run can be found in: C:\Users\faisa\AppData\Local\npm-cache\_logs\2024-06-17T06_22_43_221Z-debug-0.log
so as what these commands say let's do that
terminal npm add contentlayer next-contentlayer --force
now it should be installed (meybe in future this error get resolved but for now this is the solution ✅)
now we need to configure next.config.js
Note: In the nextjs by default next.config.mjs is there just rename it to next.config.js (it does not work with .mjs)
next.config.js const { withContentlayer } = require ( " next-contentlayer " ) ;
/** @ type { import('next').NextConfig } */
const nextConfig = {
pageExtensions : [ " js " , " jsx " , " md " , " mdx " , " ts " , " tsx " ] ,
reactStrictMode : true ,
swcMinify : true ,
};
module.exports = withContentlayer (nextConfig) ;
now change tsconfig.json
tsconfig.json {
" compilerOptions " : {
" lib " : [ " dom " , " dom.iterable " , " esnext " ] ,
" allowJs " : true ,
" skipLibCheck " : true ,
" strict " : true ,
" noEmit " : true ,
" esModuleInterop " : true ,
" module " : " esnext " ,
" moduleResolution " : " bundler " ,
" resolveJsonModule " : true ,
" isolatedModules " : true ,
" jsx " : " preserve " ,
" baseUrl " : " . " ,
" incremental " : true ,
" plugins " : [
{
" name " : " next "
}
] ,
" paths " : {
" @/* " : [ " ./* " ] ,
" contentlayer/generated " : [ " ./.contentlayer/generated " ]
}
},
" include " :
[ " next-env.d.ts " ,
" **/*.ts " ,
" **/*.tsx " ,
" .next/types/**/*.ts " ,
" contentlayer/generated " ] ,
" exclude " : [ " node_modules " ]
}
now a create a folder in you root directory
in the mdx you will write
/content/blog/my-first-blog.mdx ---
title : My first blog
date : 16th june 2024
---
Hello world here is my first blog
now the most important file "contentlayer.config.ts"
/contentlayer.config.ts import { defineDocumentType , makeSource } from " contentlayer/source-files " ;
const Blog = defineDocumentType ( () => ( {
name : " Blog " ,
filePathPattern : ` blog/**/*.mdx ` ,
contentType : ' mdx ' ,
fields : {
title : { type : " string " , required : true },
date : { type : " string " , required : true },
},
} )) ;
export default makeSource ( {
contentDirPath : " content " ,
documentTypes : [Blog]
} )
in the above file you declare the path to your content the field which you want to be definitely
want in your content.
now when you run :
terminal npm run dev
you will see
terminal ✅ Ready in 5.9s
Generated 1 documents in .contentlayer
the .contentlayer is the folder generated in your root layout
you can see what is generated by contentlayer
now how we display the generated data on our app
now get all blog from the generated .contentlayer folder and display them by title
app/page.tsx import Link from ' next/link '
import { allBlogs } from ' ../.contentlayer/generated '
export default function Home () {
const blogs = allBlogs
return (
< main className = " flex min-h-screen flex-col items-start justify-center p-24 " >
< div className = ' text-2xl mb-10 ' > All Blogs </ div >
{ blogs . map ( ( blog , index ) => (
< Link href ={ ` / ${ blog . _raw . flattenedPath }` } key ={ index } className = ' hover:underline ' >{ blog . title }</ Link >
)) }
</ main >
)
}
now create a dynamic folder
app/blog/[slug]/page.tsx import { allBlogs } from ' @/.contentlayer/generated '
export default async function Blog ({ params }: { params : any }) {
console . log ( allBlogs [ 0 ] . _raw . flattenedPath )
const blog = allBlogs . find (
( blog ) => blog . _raw . flattenedPath === ` blog/ ${ params . slug }` ,
)
if ( ! blog ) {
return
}
return (
< div className = " flex flex-col pt-28 text-white bg-zinc-900 items-center justify-start mx-auto min-h-screen px-3 md:px-0 " >
< article className = " mb-10 max-w-[700px] " >
< div className = " flex flex-col gap-8 mx-1 md:mx-0 " >
< div className = " flex max-w-2xl flex-col gap-4 text-pretty w-full " >
< h1 className = " text-3xl font-bold leading-tight tracking-tight text-left " >
{ blog . title }
</ h1 >
</ div >
</ div >
</ article >
</ div >
)
}
now when you click on rendered blog you will be taken to that blog.
now how to render the blog body as it is mdx we will be using mdx renderer to render the body
make a new folder "_components"
app/blog/_component/mdx-component.tsx
import { useMDXComponent } from " next-contentlayer/hooks " ;
interface MdxProps {
code : string ;
}
export function Mdx ({ code }: MdxProps ) {
const Component = useMDXComponent ( code ) ;
return (
< div className = " mdx " >
< Component />
</ div >
) ;
}
now update
app/blog/[slug]/page.tsx import { allBlogs } from ' @/.contentlayer/generated '
import { Mdx } from ' ../_components/mdx-component '
export default async function Blog ({ params }: { params : any }) {
const blog = allBlogs . find (
( blog ) => blog . _raw . flattenedPath === ` blog/ ${ params . slug }` ,
)
if ( ! blog ) {
return
}
return (
< div className = " flex flex-col pt-28 text-white bg-zinc-900 items-start justify-start mx-auto min-h-screen px-3 md:px-0 " >
< article className = " mb-10 max-w-[700px] p-10 " >
< div className = " flex flex-col gap-8 mx-1 md:mx-0 " >
< div className = " flex max-w-2xl flex-col gap-4 text-pretty w-full " >
< h1 className = " text-3xl font-bold leading-tight tracking-tight text-left " >
{ blog . title }
</ h1 >
</ div >
< div >
< Mdx code ={ blog . body . code } />
</ div >
</ div >
</ article >
</ div >
)
}
as you can see in line number i have added Mdx component now you body will be rendered.
Everything should be working fine by this time buts its not .
now if you try add "## Heading 2 " or "## Heading 1" nothing will work
here comes this package to rescue us tailwindcss-typography
terminal npm install -D @tailwindcss/typography --force
update tailwind.config.ts
tailwind.config.ts import type { Config } from " tailwindcss " ;
const config : Config = {
content : [
" ./pages/**/*.{js,ts,jsx,tsx,mdx} " ,
" ./components/**/*.{js,ts,jsx,tsx,mdx} " ,
" ./app/**/*.{js,ts,jsx,tsx,mdx} " ,
] ,
theme : {
extend : {
backgroundImage : {
" gradient-radial " : " radial-gradient(var(--tw-gradient-stops)) " ,
" gradient-conic " :
" conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops)) " ,
},
},
},
plugins : [ require ( ' @tailwindcss/typography ' )] ,
};
export default config ;
update this file
app/blog/[slug]/page.tsx import { allBlogs } from ' @/.contentlayer/generated '
import { Mdx } from ' ../_components/mdx-component '
export default async function Blog ({ params }: { params : any }) {
const blog = allBlogs . find (
( blog ) => blog . _raw . flattenedPath === ` blog/ ${ params . slug }` ,
)
if ( ! blog ) {
return
}
return (
< div className = " flex flex-col pt-28 text-white bg-zinc-900 items-start justify-start mx-auto min-h-screen px-3 md:px-0 " >
< article className = " mb-10 max-w-[700px] p-10 " >
< div className = " flex flex-col gap-8 mx-1 md:mx-0 " >
< div className = " flex max-w-2xl flex-col gap-4 text-pretty w-full " >
< h1 className = " text-3xl font-bold leading-tight tracking-tight text-left " >
{ blog . title }
</ h1 >
</ div >
< div className = ' prose prose-headings:text-white prose-p:text-white ' >
< Mdx code ={ blog . body . code } />
</ div >
</ div >
</ article >
</ div >
)
}
what I did above you can read here .do give a read it will be used very much
now if you add "## headind 2 " it will work
blog/my-first-blog ---
title : My first blog
date : 16th june 2024
---
Hello world here is my first blog
## Heading 2
# Heading 1
now your basic mdx website is working in next blog we will learn how to add codeblocks and out own custom components.
if you dont want to follow the whole blog you can find source code here
Thank you