The MDX blog was set up to showcase markdown content by simply displaying it without rendering, thanks to the utilization of the MDXProvider from @mdx-js/react within Next JS

I'm currently in the process of setting up a blog using MDX and Next.js, but I've encountered an issue with rendering Markdown content. The blog post seems to only display the markdown content as plain text instead of rendering it properly.

If you'd like to take a look at my full source code, you can find it here → https://github.com/deadcoder0904/blog-mdx-next/

Here is an overview of my folder structure:


|-- README.md
|-- components
|   `-- Image.js
|-- next.config.js
|-- package-lock.json
|-- package.json
|-- pages
|   |-- _app.js
|   |-- blog
|   |   `-- [slug].js
|   |-- dark.css
|   |-- index.js
|   `-- new.css
|-- posts
|   |-- blog
|   |   |-- hello-world
|   |   |   |-- Rustin_Cohle.jpg
|   |   |   `-- index.mdx
|   |   `-- shit-world
|   |       `-- index.mdx
|   `-- tutorials
|       `-- console-log-in-javascript
|           `-- index.mdx
|-- prettier.config.js
`-- utils
    `-- mdxUtils.js

All my content is stored within the posts/ folder with two subfolders: blog/ and tutorials/

Each individual post is located in its own folder inside either blog/ or tutorials/. Here is an example:

https://i.stack.imgur.com/2X5bE.png

I want my posts/blog/hello-world/index.mdx post to be rendered at the URL blog/hello-world. To achieve this, I created a blog/[slug].js file.

The contents of this file are as follows:

pages/blog/[slug].js


import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { MDXProvider } from '@mdx-js/react'

import { BLOG_PATH, blogFilePaths } from '../../utils/mdxUtils'
import { Image } from '../../components/Image'

const MDXComponents = { Image }

const Blog = ({ source, frontMatter }) => {
    return (
        <div>
            <h1>{frontMatter.title}</h1>
            <MDXProvider components={MDXComponents}>{source}</MDXProvider>
        </div>
    )
}

export async function getStaticPaths() {
    const paths = blogFilePaths.map((path) => {
        const split = path.split('/')
        const slug = split[split.length - 2]
        return {
            params: {
                slug,
            },
        }
    })

    return {
        paths,
        fallback: false,
    }
}

export const getStaticProps = async ({ params }) => {
    const { slug } = params
    const blogFilePath = path.join(BLOG_PATH, `/blog/${slug}/index.mdx`)

    const source = fs.readFileSync(blogFilePath)
    const { content: mdx, data } = matter(source)

    if (!blogFilePath) {
        console.warn('No MDX file found for slug')
    }

    return {
        props: {
            source: mdx,
            frontMatter: data,
        },
    }
}

export default Blog

The crucial part of this code snippet is:

<MDXProvider components={MDXComponents}>{source}</MDXProvider>

Although I expected this to render the markdown content, it appears to only display it as plain text. To see this behavior in action, you can check out the output on this link by clicking on any blog post.

When I click on the "Hello World" post, the following is displayed:

https://i.stack.imgur.com/E2BJc.png

How can I actually render the markdown content correctly?

I have looked at other repositories such as the Tailwind CSS Blog and it seems to work perfectly for them. Unfortunately, I am unsure about the specifics of how it works. What I do know is that I need to convert the source prop in the Blog component or the mdx variable in getStaticProps, but I couldn't identify this step in the Tailwind codebase either.

Answer №1

Here is the solution you've been searching for: https://github.com/vercel/next.js/discussions/13901

To implement this, you'll have to install the following package: npm i next-mdx-remote and make these modifications in your code:

import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import renderToString from 'next-mdx-remote/render-to-string'
import hydrate from 'next-mdx-remote/hydrate'

import { BLOG_PATH, blogFilePaths } from '../../utils/mdxUtils'
import { Image } from '../../components/Image'

const components = { Image }

const Blog = ({ source, frontMatter }) => {
    const content = hydrate(source, { components })

    return (
        <div>
            <h1>{frontMatter.title}</h1>
            {content}
        </div>
    )
}

...

export const getStaticProps = async ({ params }) => {
    const { slug } = params
    const blogFilePath = path.join(BLOG_PATH, `/blog/${slug}/index.mdx`)

    const source = fs.readFileSync(blogFilePath, 'utf-8');
    const { content, data } = matter(source);
    const mdxSource = await renderToString(content, { components });

    if (!blogFilePath) {
        console.warn('No MDX file found for slug')
    }

    return {
        props: {
            source: mdxSource,
            frontMatter: data,
        },
    }
}

export default Blog

However, there might be an issue when referencing assets within your mdx files. The tailwind blog addresses this by adding a loader for assets. Unfortunately, next-mdx-remote does not appear to support imports inside MDX files (or may require specific configuration), so you will need to relocate your images to the public folder, e.g., public/blog/Rustin_Cohle.jpg

The key distinction from the tailwind blog is that you are utilizing Dynamic SSG.

Additionally, there is another untested approach that you could explore further: https://github.com/vercel/next.js/issues/9524#issuecomment-580239600

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to align content in the left center of a Paper component and ensure it stays that way on smaller devices?

Recently, I've been developing a component for my Goal Sharing social media platform. Here's what I have accomplished so far: https://i.stack.imgur.com/UDRim.png In an attempt to position the Avatar component along with two typography component ...

Guide to swapping images based on conditions using Angular JS

I am trying to display an image based on data received from an API response instead of text. If the value is true, I want to show an access symbol. If the value is false, I want to show a deny symbol. However, when attempting this, I am only getting th ...

Using jQuery to insert a new string into the .css file

I'm currently working on a jQuery function to make my font size responsive to changes in width. While I am aware of other options like Media Query, I prefer a solution that offers smoother transitions. Using vw or vh units is not the approach I want t ...

Press anywhere on the screen to conceal the AngularJS element

Attempting to create a toggle effect using 2 ng-click functions. One is bound to a button, the other to the body tag. The goal is for my content to show when the button is clicked and hide when anywhere on the body is clicked. However, it seems that Angul ...

Creating a scrolling effect similar to the Nest Thermostat

After researching countless references, I am determined to achieve a scrolling effect similar to the Nest Thermostat. I came across this solution on this JSFiddle, but unfortunately, the parent element has a fixed position that cannot be utilized within my ...

What is the best way to organize angularjs controllers and directives within one another?

When I structure my controllers like this: <body ng-app="app" ng-controller="ctrl"> <div ng-controller="app-get"> <app-get></app-get> </div> <div ng-controller="app-post"> <app-post">& ...

Does the useState hook have any connection to hoisting in React?

I am relatively new to React.js and JavaScript, currently working on a project where I need the ability to manually update my components as needed, due to limitations with a third-party library. After doing some research, I came across a pattern on the of ...

Reloading a Nuxt.js page triggers the fetch function

I'm facing an issue with nuxt.js fetch(). Whenever I reload the page, it does not fetch the data again. It only fetches if I come from a router link. How can I force it to actually refetch the data from my API? export default { async fetch() { ...

Issues with hover functionality in Javascript, CSS, and HTML

Seeking assistance with my JavaScript, HTML, and CSS development, I ran into an issue while trying to create a hovering function for my webpage. Despite my efforts, the links are not displaying anything when hovered over, and the divs meant for specific ho ...

What is the best way to obtain the output of a JavaScript function on the server side?

I'm dealing with a JavaScript function that returns an array in this particular format: <script type="text/javascript"> function looping() { var column_num = 1; var array = []; $("#columns ul").not(" ...

Monaco Editor: Module 'monaco-editor' is missing despite being successfully installed

During the development of my desktop application with electron, I encountered an issue with installing Monaco Editor. After using npm install monaco-editor, running the application resulted in a message saying Cannot find module 'monaco-editor'. ...

Creating a GET request to search for multiple properties in MongoDB using React and Node.js

In my node.js react application, I've been experimenting with the db.collections.find function. This application involves searching and hiring teachers for various subjects. My goal is to create a user interface that presents 3 select boxes: one for t ...

What could be causing the glitches I'm encountering while attempting to develop a React application with 'create-react-app'?

Recently, I set up my new PC and wanted to start writing React code. After installing npm, the next step was to run npx create-react-app. However, every time I tried, I encountered this error message: $ npx create-react-app code-app npm ERR! code ENOENT np ...

The difference between emitting and passing functions as props in Vue

Imagine having a versatile button component that is utilized in various other components. Instead of tying the child components to specific functionalities triggered by this button, you want to keep those logics flexible and customizable within each compon ...

The typescript error "Cannot read properties of undefined" is encountered while trying to access the 'map' function

I was attempting to follow a guide on creating an app using typescript and react, but I'm encountering an error that says "Cannot read properties of undefined (reading 'map')". I'm not sure why this is happening, can someone please offe ...

Next Js (version 13.4.4-canary.2) is reporting that NEXT_PUBLIC_BASE_URL is currently undefined

I currently have two .env files set up: NEXT_PUBLIC_BASE_URL=http://localhost:3000 One is a simple .env file and the other is a .env.local file. NEXT_PUBLIC_BASE_URL=http://localhost:3000 Within my project, I am utilizing the new nextjs app folder struct ...

The Access-Control-Allow-Origin error is preventing the Angularjs post request from going through

I am encountering an issue with my index.html file that is sending a post request to localhost:3000/SetUser. The error I keep receiving states XMLHttpRequest cannot load https://localhost:3000/SetUser. No 'Access-Control-Allow-Origin' header is p ...

The vanilla JS router seems to be malfunctioning, as it is only showing the [object XMLDocument]

Recently, I attempted to implement routing into my JavaScript application using the following example: link. However, after diligently copying every file from the project, all I see displayed inside the <main></main> tags is "[object XMLDocum ...

The button component is unresponsive

An app was developed with a component containing signin and signup buttons on the right side. However, there is an issue where the Sign up button is not clickable. Below is the code snippet for the component => import React from "react"; expo ...

Is the examples folder missing from the repository after installing react-router using npm?

After executing the npm install react-router command, it installs version react-router v1.0.3 which includes an examples directory. However, when running the commands: cd node_modules/react-router ls The resulting output is: CHANGES.md README.m ...