Using markdown to manage content to NextJS App Router
Tags: nextjs, react, typescript, markdown
Published: 2024-03-26 02:12:43

Recently I have been building my portfolio site using NextJs/TailwindCSS.
Here is the link to the site - [Visit here](https://portfolio-beta-ten-13.vercel.app/)
It's still a work in progress, here is the [source code](https://github.com/MitraKumar/portfolio?tab=readme-ov-file) 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](https://nextjs.org/docs/pages/building-your-application/configuring/mdx) 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

```bash
npm install next-mdx-remote
```

### 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.

```tsx

// page.tsx

export default async function IndexPage() {

  const markdown_data = await getProject();

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

}

```

### 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.

```ts
// 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 || {}) }}
    />
  );
}

```

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

For example...
```tsx
// 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>
  );
}
```

```tsx
// 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>
  );
}

``` 

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

```tsx
// /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} />
    </>
  );

}
```

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


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

...

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

```

The source code for the above can be found [here](https://github.com/MitraKumar/portfolio)


## 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.