1

I am trying to create an MDX blog and integrate it into my project. I currently have most of the setup done but I cannot for the life of me get MDX file into a component to render between my Layout. Here's my issue, all of the examples showcase something weird, for example,

MDX docs aren't typed, and I cannot get this to work in typescript

export default function Page({code}) {
  const [mdxModule, setMdxModule] = useState()
  const Content = mdxModule ? mdxModule.default : Fragment

  useEffect(() => {
    ;(async () => {
      setMdxModule(await run(code, runtime))
    })()
  }, [code])

  return <Content />
}

And Nextjs just advises to put mdx files under /pages/, which doesn't fit my usecase

What I want:

  1. Have an MDX file with Frontmatter YAML metadata
  2. Load that MDX file into a component and display it

What I have currently

Nextjs config

import mdx from '@next/mdx';
import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm';

/** @type {import('next').NextConfig} */
const config = {
  eslint: {
    dirs: ['src'],
  },

  reactStrictMode: true,
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'tsx', 'ts'],

  images: {
    domains: [
      'res.cloudinary.com',
      'picsum.photos', //TODO
    ],
  },

  // SVGR
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.svg$/i,
      issuer: /\.[jt]sx?$/,
      use: [
        {
          loader: '@svgr/webpack',
          options: {
            typescript: true,
            icon: true,
          },
        },
      ],
    });
    config.module.rules.push({
      test: /\.mdx?$/,
      use: [
        options.defaultLoaders.babel,
        {
          loader: '@mdx-js/loader',
          options: {
            //  providerImportSource: '@mdx-js/react',
            remarkPlugins: [remarkFrontmatter, remarkGfm],
          },
        },
      ],
    });

    return config;
  },
};

export default config;

My projects/[slug].tsx file

import { GetStaticPaths, GetStaticProps } from 'next';

import { getFileBySlugAndType, getFiles } from '@/lib/mdx/helpers';

import { Layout } from '@/components/layout/Layout';
import Seo from '@/components/Seo';
import * as runtime from 'react/jsx-runtime.js'

import { ProjectFrontMatter } from '@/types/frontmatter';
import { useEffect, useState } from 'react';
import {compile, run} from '@mdx-js/mdx'
import { MDXContent } from 'mdx/types';

type SingleProjectPageProps = {
  frontmatter: ProjectFrontMatter;
  content: string;
};

export default function SingleProjectPage({
  frontmatter,
  content,
}: SingleProjectPageProps) {

  return (
    <Layout>
      <Seo
        templateTitle={frontmatter.name}
        description={frontmatter.description}
        date={new Date(frontmatter.publishedAt).toISOString()}
      />

      <main>
        <section className=''>
          <div className='layout'>
            <div className='mt-8 flex flex-col items-start gap-4 md:flex-row-reverse md:justify-between'>
              {/* <CustomLink
                href={`https://github.com/theodorusclarence/theodorusclarence.com/blob/main/src/contents/projects/${frontmatter.slug}.mdx`}
              >
                Edit this on GitHub
              </CustomLink>
              <CustomLink href='/projects'>← Back to projects</CustomLink> */}
            </div>
          </div>
        </section>
      </main>
    </Layout>
  );
}

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await getFiles('projects');
  return {
    paths: posts.map((p) => ({
      params: {
        slug: p.replace(/\.mdx/, ''),
      },
    })),
    fallback: false,
  };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const slug = params!['slug'] as string;
  const { data, content } = getFileBySlugAndType(slug, 'projects');
  return {
    props: { frontmatter: data as ProjectFrontMatter, content },
  };
};

And my helper function


export const getFileBySlugAndType = (slug: string, type: ContentType) => {
  const file = readFileSync(
    join(process.cwd(), 'src', 'content', type, slug + '.mdx'),
    'utf-8'
  );

  const { data, content } = matter(file);

  return { data, content };
};

I get as props in my SingleProjectPage, frontmatter data, and a string of the rest of the MDX content, which is correct, but I need to turn this string into MDX component. One of the libraries that does this is MDX-Bundler, but it hasn't been updated this year, and I'd prefer to use mdx-js if possible as it just release 2.0 version.

vldmrrdjcc
  • 2,082
  • 5
  • 22
  • 41
Nikola-Milovic
  • 1,393
  • 1
  • 12
  • 35

0 Answers0