Using markdown to manage content to NextJS App Router

mitrakumar

Kaushik Mitra

Posted on March 26, 2024

Using markdown to manage content to NextJS App Router

Recently I have been building my portfolio site using NextJs/TailwindCSS.
Here is the link to the site - Visit here
It's still a work in progress, here is the source code for the site.

In the site I have used markdown files to manage the Projects data on the site.

Today we will discuss how we can use the markdown files to generate contents for a NextJS site with Server Components and integrate with Typescript.

So let's dive in...


Why next-mdx-remote...

If we follow the official documentation there it is mentioned that how we can basically use .mdx to generate our JSX components which we can render on our site.

But following this way can be a bit difficult as it will be very difficult to manage dynamically generated content or if the markdown is coming from somewhere else like some CMS or some other place from the file system.

To solve this issue we can use a community package next-mdx-remote. This package actually compiles a given markdown string and generates HTML, so it will solve our problem of maintaining markdown if we want to scale in future and it is coming from some other database or something.

Integrating next-mdx-remote to React Server components in NextJS.

Installation

npm install next-mdx-remote
Enter fullscreen mode Exit fullscreen mode

Basic Usage

As we are using react server components we can simply fetch the markdown data from a file and display it with the MDXRemote component.


// page.tsx

export default async function IndexPage() {

  const markdown_data = await getProject();

  if (markdown_data === "") {
    return notFound();
  }

  return (
    <>
     <MDXRemote source={markdown_data} />
    </>
  );

}

Enter fullscreen mode Exit fullscreen mode

Advance Usage

We can setup our own custom components when generating the HTML form the Markdown files.

For this we can create our own component which will wrap the libraries MDXRemote component.

// mdx-remote.ts

import { MDXRemote, MDXRemoteProps } from "next-mdx-remote/rsc";

const components = {
  h1: (props: ComponentProps<"h1">) => <HeaderOne {...props}>{props.children}</HeaderOne>,
  h2: (props: ComponentProps<"h2">) => <HeaderTwo {...props}>{props.children}</HeaderTwo>,
  p: (props: ComponentProps<"p">) => <Paragragraph {...props}>{props.children}</Paragragraph>,
  img: (props: ComponentProps<"img">) => <MdxImage {...props} />,
  a: (props: ComponentProps<"a">) => <MdxLink {...props}>{props.children}</MdxLink>,
};

export function CustomMDX(props: MDXRemoteProps) {
  return (
    <MDXRemote
      {...props}
      components={{ ...components, ...(props.components || {}) }}
    />
  );
}

Enter fullscreen mode Exit fullscreen mode

And these HeaderOne, HeaderTwo, etc can be simple components with our custom styles.

For example...

// HeaderOne.tsx

import { ComponentProps } from "react";

export function HeaderOne({ children, ...props }: ComponentProps<"h1">) {
  return (
    <h1 {...props} className="mb-6 mt-16 text-5xl">
      {children}
    </h1>
  );
}
Enter fullscreen mode Exit fullscreen mode
// MDXImage.tsx

import Image from "next/image";
import { ComponentProps } from "react";

type MdxImageProps = ComponentProps<"img">;
export function MdxImage(props: MdxImageProps) {
  return (
    <div className="flex justify-center">
      <div className="border-4 border-primary">
        <Image
          {...props}
          src={props.src ?? ""}
          alt={props.alt ?? ""}
          width={500}
          height={300}
        />
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now we have created our custom wrapper component for the MDXRemote we can use this in our server component.

// /projects/[title]/page.tsx

export default async function IndexPage({
  params,
}: {
  params: { title: string };
}) {

  const markdown_data = await getProjectWithTitle(params.title);

  if (markdown_data === "") {
    return notFound();
  }

  return (
    <>
     <MDXRemote source={markdown_data} />
    </>
  );

}
Enter fullscreen mode Exit fullscreen mode

Now as we are using NextJS we can further more optise our site by staticly generating the page with generateStaticParams function

// /projects/[title]/page.tsx

...

export async function generateStaticParams() {
  return getProjectTitles();
}

Enter fullscreen mode Exit fullscreen mode

The source code for the above can be found here

Conclusion

So, in conclusion we discussed how we can integrate this community package next-mdx-remote for managing content with markdown and also how we can use Typescript to add proper types and have proper type safe development.

If you have any discussions regarding the topic feel free to comment below.

💖 💪 🙅 🚩
mitrakumar
Kaushik Mitra

Posted on March 26, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related