Intro
Today, we will build a blog website with Next.js, Tailwind, and MDX without using a CMS at all!
What is MDX?
MDX is a combination of Markdown and JavaScript. MDX allows you to use JSX in your markdown content. You can import components, such as interactive charts or alerts, and embed them within your content. This makes writing long-form content with components a blast.
Why will we use MDX?
We will use MDX because it’s so powerful, and you can write more interactive articles with it! Also, you will not need to pay any money for a CMS!
Let’s Start
Here is the project that we will build using Next.js, Tailwind CSS and MDX Blog.
First, let’s create a new Next.js project:
npx create-next-app next-blog
After it’s done, go to the project directory:
cd next-blog
Then, run the dev server:
npm run dev
Install Tailwind CSS
Installing Tailwind CSS with Next.js is simple.
First, we need to add some dev dependencies.
npm install -D tailwindcss postcss autoprefixer
Then, Initialize Tailwind CSS.
npx tailwindcss init -p
You will find that there are two new files that have been added to the project
tailwind.config.js
postcss.config.js
Now, we need to tell Tailwind CSS where to find the classes. Go to tailwind.config.js
, and add these lines under content
.
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
Go to the styles/globals.css
and add these three lines:
@tailwind base;
@tailwind components;
@tailwind utilities;
And restart the dev server. You will find that the design has been changed like that:
That means it has been installed successfully 🎉
Build the Layout
We will create a very simple layout. Just add the main content in between a header and a footer.
Let's create a new folder called components
, and add three new components inside it.
Layout.js
Header.js
Footer.js
I'll leave the Header.js
and Footer.js
to you. Let's start from Layout.js
:
import Header from "./Header";
import Footer from "./Footer";
function Layout({ children }) {
return (
<div className="container mx-auto max-w-5xl flex flex-col min-h-screen px-4">
<Header />
<main className="flex-1">{children}</main>
<Footer />
</div>
);
}
export default Layout;
Important Tip: For some accessibility each page should have one
<header></header>
, one<main></main>
, and one<footer></footer>
.
Next Step, we need to wrap the whole application with our Layout.js
but how?
This is possible in Next.js since the pages/_app.js
is a wrapper for the whole app.
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
export default MyApp;
Finally let's go to pages/index.js
and delete everything with this code.
export default function Home() {
return (
<div>
<h1>Hello world</h1>
</div>
);
}
getPosts
function
We have the layout now! Let's work on the functionality.
Create a new folder in the root directory and call it posts
. Then, add some articles.
Then, create another folder called helpers
where we will add most used functions.
Create a new file helpers/getPosts.js
. After it, we will need to install a package called gray-matter
. It's necessary to parse the mdx file content.
npm i gray-matter
And we will use this package to separate between the frontmatters, and the actual content of each post.
We need only three important items in each post: title
, date
and description
. For example:
And this is how gray-matter
works:
import matter from "gray-matter";
const { data, content } = matter(fileContent)
where data
is an object with the frontmatters data and content
is the actual post.
Second, we need to get a list of all the files inside the posts
folder.
import fs from "fs";
import path from "path";
const files = fs.readdirSync(path.join("posts"));
files
should return an array with the file names.
[
"hello-world.mdx",
"markdown-guide.mdx"
]
Then, we need to loop through to get each one's data one by one.
const allPostsData = files.map((fileName) => {
const slug = fileName.replace(".mdx", "");
const fileContents = fs.readFileSync(
path.join(`posts/${slug}.mdx`),
"utf8"
);
const { data } = matter(fileContents);
return {
slug,
data,
};
});
And return allPostsData
from the getPosts
function.
getPost
function
It's a simpler version from getPosts
function, we will pass the slug, and it should return the post data and content.
const getPost = (slug) => {
const fileContents = fs.readFileSync(path.join(`posts/${slug}.mdx`), "utf8");
const { data, content } = matter(fileContents);
return {
data,
content,
};
};
Display the posts menu on the home page
First, we need to create getStaticProps
function on the home page.
export const getStaticProps = () => {
const posts = getPosts();
return {
props: {
posts,
},
};
};
Then, let's get the posts in the page component.
export default function Home({ posts }) {
return (
<div>
<h1 className="mt-24 mb-12 font-bold text-3xl">Latest Posts</h1>
{posts.map((post) => (
<PostCard
key={post.slug}
title={post.data.title}
date={post.data.date}
description={post.data.description}
slug={post.slug}
/>
))}
</div>
);
}
The post page
This is the final step in the project when we will display the posts. In Next.js, to add a page with parameter in its path, you should name it between brackets. In our example, we will add [slug].js
in the pages directory.
We need to handle things in this page:
getStaticPaths
:
We need this function in Next.js to tell it how many pages should be build in the build time from this page.
export const getStaticPaths = async () => {
const posts = await getPosts();
const paths = posts.map((post) => ({ params: { slug: post.slug } }));
return {
paths,
fallback: false,
};
};
Install next-mdx-remote
It allows us to compile the MDX to HTML.
npm i next-mdx--remote
getStaticProps
:
import { serialize } from "next-mdx-remote/serialize";
export const getStaticProps = async ({ params }) => {
const post = await getPost(params.slug);
const mdxSource = await serialize(post.content);
return {
props: {
data: post.data,
content: mdxSource,
},
};
};
use MDXRemote
to display the post:
import { MDXRemote } from "next-mdx-remote";
function Post({ data, content }) {
return (
<div>
<h1 className="font-bold text-7xl mt-24 mb-12">{data.title}</h1>
<time className="text-gray-500 italic">{data.date}</time>
<p className="mt-12">
<MDXRemote {...content} />
</p>
</div>
);
}
Style the post content
After applying all of the above, you will find the post page like this.
So we have to style it, install @tailwindcss/typography
plugin.
npm i @tailwindcss/typography
Then, add it to plugins in tailwind.config.js
.
plugins: [
require("@tailwindcss/typography")
]
Finally, add the prose
class to the MDXRemote
container.
Final result:
Here, you can find the code for the complete project: Next.js, Tailwind CSS and MDX Blog