Make a blog with Nextjs , tailwindcss and mdx

2023-02-01

I am using Nestjs 13 with page , not the new App Dir , tailwindcss and mdx

Nextjs

Next. js is a JavaScript framework that is built on top of React, a popular library for building user interfaces. This means that you can use React to build your app, and Next. js provides additional tools and features to make the process easier. One of the main benefits of Next.

MDX :

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.

TailwindCss

Tailwind CSS is an open source CSS framework. The main feature of this library is that, unlike other CSS frameworks like Bootstrap, it does not provide a series of predefined classes for elements such as buttons or tables.

And I am using some npm packages that help me in showing the data . these packages are :

gray-matter :

Parse front-matter from a string or file. in out case mdx file and formate it so we can get the frontMatter which is the part between --- --- , and the content which is the body of the post .

rehype-highlight :

plugin to apply syntax highlighting to code with highlight.js. highlight.js is pretty fast, relatively small, and a quite good syntax highlighter which has support for up to 190 different languages.It looks for <code></code> elements (when directly in <pre></pre> elements) and changes them. You can specify the code language (such as JavaScript)

<pre><code class="hljs language-js"><span class="hljs-comment">/** <span class="hljs-doctag">@type</span> {<span class="hljs-type">import('tailwindcss').Config</span>} */</span>
<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {
  <span class="hljs-attr">content</span>: [
    <span class="hljs-string">"./pages/**/*.{js,ts,jsx,tsx}"</span>,
    <span class="hljs-string">"./components/**/*.{js,ts,jsx,tsx}"</span>,
  ],
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [],
};
</code></pre>

@tailwindcss/typography

The official Tailwind CSS Typography plugin provides a set of prose classes you can use to add beautiful typographic defaults to any vanilla HTML you don’t control, like HTML rendered from Markdown

tailwind-highlightjs

used after rehydrate the code block , to give it a style and colors depending on the theme that you will choose .

next-mdx-remote

next-mdx-remote changes the entire pattern so that you load your MDX content not through an import, but rather through getStaticProps or getServerProps -- you know, the same way you would load any other data. The library provides the tools to serialize and hydrate the MDX content in a manner that is performant.

Steps :

  • install Nextjs 13 y : note that typescript and eslint are optionl but recomended, during the installation nextjs will give you some options like if you want to make app Dir ( answer yes) but I am not using it now , because it gives me many issues during deployment, ( answer no )
npx create-next-app@latest my-blog --typescript --eslint
cd my-blog
  • now install tailwind css
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

in tailwind.config.js edite the configuraiotn

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

in app/globals.css remove everything and add :

@tailwind base;
@tailwind components;
@tailwind utilities;

Run nextjs app by the command:

npm run dev

Lets add some dummy mdx files in a new folder called content you can use the one in my project on github. I just add another folder in content called blog , in case you want to use your blog to show you projects for example , then you just add another folder with name projects and use it in the same way that we will continue with .

now lets fetch these files and react it , in pages make a new directory with name blog and in it make a new file index.js where we will show some posts .

Fetching out data will be using getStaticProps , note that using fs and path will be only possible in this function which conseder backend or server side function , and use it ( fs, path) is not possible in the frontend , I wil sort the results by date using sort funtion .

import path from "path";
import fs from "fs";
import matter from "gray-matter";

export async function getStaticProps() {
  const postsFiles = fs.readdirSync(path.join(`content/blog`));
  const posts = postsFiles.map((fileName) => {
    const postsMarkdownWithMeta = fs.readFileSync(
      path.join(`content/blog`, fileName),
      "utf-8"
    );
    const slug = fileName.split(".")[0];
    const { data: frontMatter, content } = matter(postsMarkdownWithMeta);
    return {
      slug,
      frontMatter,
      content,
    };
  });

  return {
    props: {
      posts: posts.sort((a, b) => {
        return (
          Number(new Date(b.publishedAt)) - Number(new Date(a.publishedAt))
        );
      }),
    },
  };
}

Lets add the posts that we got to the frontend function Post , and use tailwind to add some style.

import Link from "next/link";
import Image from "next/image";

export default function Blog({ posts }) {
  return (
    <div className="flex flex-col justify-center items-center max-w-3/4 mt-[60px] ">
      <h1 className="text-3xl font-medLarg text-primary text-center my-3 underline text-shadow-md ">
        Latest Blog Articles
      </h1>
      <div className="my-4 w-full">
        {posts.map((post) => {
          return (
            <Link
              href={`/blog/${post.slug}`}
              key={post.frontMatter.slug}
              className="flex flex-col justify-center items-center my-1 md:w-3/4 md:mx-auto"
            >
              <div className="bg-gray-50  m-1 px-3 py-2 rounded-lg shadow-md w-full hover:bg-gray-200">
                <span className="font-bold text-xl font-medLarg hover:underline mb-3 capitalize md:text-2xl">
                  {post.frontMatter.title}
                </span>
                <br />
                <span className=" text-md mb-3 capitalize md:text-2xl">
                  {post.frontMatter.excerpt}
                </span>
                <div className="flex  mt-2 gap-1 items-center">
                  <span className="rounded-lg bg-red-200 px-2 py-1 font-medLarg">
                    {post.frontMatter.publishedAt}
                  </span>
                  <div className="flex">
                    {post.frontMatter.tages.map((tag, index) => {
                      return (
                        <span
                          key={`post-tags-item-${index}`}
                          className="rounded-lg bg-gray-400 m-1 px-2 py-1 text-white"
                        >
                          {tag}
                        </span>
                      );
                    })}
                  </div>
                </div>
              </div>
            </Link>
          );
        })}
      </div>
    </div>
  );
}

you can see that if you click on the post , it will lead you to the post details , so we need to make the paths for all the posts that you have , because Nextjs will generate it on build time , and I will use the slug of the post ( which is in simple words the title with - between its words )

  • Make a new dir in the blog dir with name [slug] , note that this name is important , because it need to match the slug that we generate , so if you want to name it project for example , then you need to change the name of the path to projct instead of slug .

now in the [slug] dir make a new file index.js , and add in it:

import rehypeHighlight from "rehype-highlight";
import path from "path";
import fs from "fs";
import matter from "gray-matter";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";

export default function BlogItem({ mdxContent, frontMatter }) {
  return (
    <div className="flex justify-center mx-auto items-center flex-col my-4  p-2 w-full px-2 overflow-hidden">
      <div className="h-48 bg-[#4a91cf] rounded-lg flex justify-center items-center flex-col px-4 py-2 shadow-md my-7 w-full">
        <h1 className="font-medLarg text-3xl font-serif text-gray-100 mt-2">
          {frontMatter.title}
        </h1>
        <div className="grid grid-cols-[auto_1fr_auto] items-center mt-4 mb-8 font-mono text-sm text-gray-900">
          <div className="bg-neutral-100 dark:bg-neutral-800 rounded-md px-2 py-1 tracking-tighter">
            {frontMatter.publishedAt}
          </div>
        </div>
      </div>

      <article className="prose prose-stone prose-headings:capitalize prose-a:text-blue-600 w-full px-3">
        <MDXRemote {...mdxContent} />
      </article>
    </div>
  );
}

export async function getStaticPaths() {
  const files = fs.readdirSync(path.join("content/blog"));
  const paths = files.map((fileName) => ({
    params: { slug: fileName.replace(".mdx", "") },
  }));

  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({ params: { slug } }) {
  const markdownWithMeta = fs.readFileSync(
    path.join("content/blog", slug + ".mdx"),
    "utf-8"
  );
  const { data: frontMatter, content } = matter(markdownWithMeta);
  const mdxContent = await serialize(content, {
    mdxOptions: {
      rehypePlugins: [
        [
          rehypeHighlight,
          {
            ignoreMissing: true,
          },
        ],
      ],
    },
  });
  return {
    props: {
      frontMatter,
      mdxContent,
    },
  };
}

everything is ready , we just need to syle the code block using tailwind typography and highlight , so in tailwind.config.js modify the code, note that I am using an adaptive fontWeight and this is optional .

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  safelist: [
    {
      pattern: /hljs+/,
    },
  ],
  theme: {
    fontWeight: {
      veryLarg: "900",
      medLarg: "700",
    },
    hljs: {
      theme: "stackoverflow-dark",
    },
    extend: {},
  },
  plugins: [
    require("@tailwindcss/typography"),
    require("tailwind-highlightjs"),
  ],
};

Finaly I will just update the index.js in the page dir , by adding nav so we can navigate between the home page and the blog

<nav className="w-2/3 mx-auto bg-gray-900 text-gray-100 flex justify-between ">
  <Link href={"/"}></Link>
  <Link href={"/blog"}></Link>
</nav>

Thanks for reading :)