Reputation: 66
I'm trying to use Kent C Dodds mdx-bundler on Next.js typescript blog starter example. It seems to render JSX and certain markdown ok but most of the simple markdown syntax like lists and adding spaces to create paragraphs are not working. I'm completely lost as to why it's doing this!
Here's the example mdx file i'm using to test:
---
title: "My first post"
description: "testing mdx"
publishedAt: "2021-12-23"
---
Skeleton component:
import {Skeleton} from '../components/Skeleton'
<Skeleton />
# Heading 1
## Heading 2
Heading
============
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Unordered list:
- a
- b
Ordered list:
1. one
2. two
**bold** and *italic* and ***bold italic***
~~strikethrough~~
Horizontal rule:
*******
Blockquote:
> Lorem ipsum dolor sit amet, consectetur adipiscing elit
>
>> Lorem ipsum dolor sit amet, consectetur adipiscing elit
indented code block:
return false;
backtick code block:
\``` (escaped here for stackoverflow)
return true;
\```
Image:

Here's the output i get:
Here's the api.ts
file that reads the contents of my mdx:
import fs from 'fs'
import { format, parseISO } from "date-fns"
import matter from 'gray-matter'
import path from "path"
import glob from 'glob'
import gfmPlugin from 'remark-gfm'
import remarkBreaks from 'remark-breaks'
import { bundleMDX } from "mdx-bundler"
import { PostMeta } from '../types/post'
const ROOT_PATH = process.cwd()
export const POSTS_PATH = path.join(ROOT_PATH, "posts")
export const getAllPostsMeta = () => {
const PATH = path.join(POSTS_PATH)
// Get all file paths in the posts folder (that end with .mdx)
const paths = glob.sync(`${PATH}/**/*.mdx`)
return (
paths
.map((filePath): PostMeta => {
// Get the content of the file
const source = fs.readFileSync(path.join(filePath), "utf8")
// Get the file name without .mdx
const slug = path.basename(filePath).replace(".mdx", "")
// Use gray-matter to extract the post meta from post content
const data = matter(source).data as PostMeta
const publishedAtFormatted = format(
parseISO(data.publishedAt),
"dd MMMM, yyyy",
)
return {
...data,
slug,
publishedAtFormatted,
}
})
// Sort posts by published date
.sort(
(a, b) =>
Number(new Date(b.publishedAt)) - Number(new Date(a.publishedAt)),
)
)
}
// Get content of specific post
export const getPostBySlug = async (slug: string) => {
// Get the content of the file
const source = fs.readFileSync(path.join(POSTS_PATH, `${slug}.mdx`), "utf8")
const { code, frontmatter } = await bundleMDX({
source,
xdmOptions(options) {
options.remarkPlugins = [
...(options?.remarkPlugins ?? []),
gfmPlugin,
remarkBreaks,
]
return options
},
esbuildOptions(options) {
options.target = "esnext"
return options
},
})
const publishedAtFormatted = format(
parseISO(frontmatter.publishedAt),
"dd MMMM, yyyy",
)
const meta = {
...frontmatter,
publishedAtFormatted,
slug,
} as PostMeta
return {
meta,
code,
}
}
This is the [slug].tsx
file for rendering the selected post:
import React from 'react';
import Container from '../../components/container'
import Header from '../../components/header'
import PostHeader from '../../components/post-header'
import Layout from '../../components/layout'
import { format, parseISO } from "date-fns"
import { getMDXComponent } from "mdx-bundler/client"
import { GetStaticProps } from 'next';
import { getPostBySlug, getAllPostsMeta } from '../../lib/api'
import Head from 'next/head'
import type { Post } from "../../types/post"
import SectionSeparator from '../../components/section-separator'
import { WEBSITE_SHORT_URL } from '../../lib/constants';
export const getStaticPaths = () => {
const posts = getAllPostsMeta()
const paths = posts.map(({ slug }) => ({ params: { slug } }))
return {
paths: paths,
// Return 404 page if path is not returned by getStaticPaths
fallback: false,
}
}
export const getStaticProps: GetStaticProps<Post> = async (context) => {
const slug = context.params?.slug as string
const post = await getPostBySlug(slug)
return {
props: {
...post,
publishedAtFormatted: format(
parseISO(post.meta.publishedAt),
"dd MMMM, yyyy",
),
},
}
}
export default function PostPage({ meta, code }: Post) {
const Component = React.useMemo(() => getMDXComponent(code), [code]);
return (
<Layout>
<Container>
<Header />
<article className="mb-32">
<Head>
<title>
{meta.title} | {WEBSITE_SHORT_URL}
</title>
</Head>
<PostHeader
title={meta.title}
coverImage={meta.coverImage}
date={meta.publishedAt}
/>
<div className="max-w-4xl mx-auto">
<Component
components={{
SectionSeparator
}}
/>
</div>
</article>
</Container>
</Layout>
);
}
Other things that may be of use...
My Next.js folder structure:
+-- next/
+-- @types/
+-- components/
+-- lib
| +-- api.ts
+-- node_modules/
+-- pages
| +-- posts
| | +-- [slug].tsx
| +-- _app.tsx
| +-- _document.tsx
| +-- index.tsx
+-- posts
| +-- my-first-post.mdx
+-- public/
+-- styles/
+-- types/
+-- .gitignore
+-- next-env.d.ts
+-- package-lock.json
+-- package.json
+-- postcss.config.js
+-- tailwind.config.js
+-- tsconfig.json
Package.json:
{
"private": true,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"typecheck": "tsc"
},
"dependencies": {
"@headlessui/react": "^1.4.2",
"@mdx-js/loader": "^1.6.22",
"@next/mdx": "^12.0.7",
"@reduxjs/toolkit": "^1.7.1",
"axios": "^0.24.0",
"classnames": "2.3.1",
"clsx": "^1.1.1",
"date-fns": "2.21.3",
"esbuild": "^0.13.15",
"glob": "^7.2.0",
"gray-matter": "4.0.3",
"mdx-bundler": "^8.0.0",
"next": "latest",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hook-form": "^7.22.1",
"react-redux": "^7.2.6",
"react-scroll": "^1.8.4",
"react-use": "^17.3.1",
"react-use-scroll-direction": "^0.1.0",
"remark": "13.0.0",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"remark-html": "~13.0.1",
"typescript": "^4.2.4"
},
"devDependencies": {
"@types/glob": "^7.2.0",
"@types/jest": "^26.0.23",
"@types/node": "^15.6.0",
"@types/react": "^17.0.6",
"@types/react-dom": "^17.0.5",
"@types/react-scroll": "^1.8.3",
"autoprefixer": "^10.2.5",
"postcss": "^8.3.0",
"tailwindcss": "^2.1.2"
}
}
Upvotes: 0
Views: 3461
Reputation: 73
I had the same issue and I was able to fix it by using a package called @tailwindcss/typography. so first install the package.
npm install @tailwindcss/typography
after that you need to add it as a plugin to the tailwind config file
plugins: [
require('@tailwindcss/typography')
],
for more detail this blog definitely helped me. here's the link
Upvotes: 5
Reputation: 66
Ok got this problem fixed by piping in a custom component with custom css to handle the vanilla markdown syntax (shoutout to homu from Next.js discord for the help).
In MDXComponents.tsx
:
export const components = {
h1: (props) => <h1 className="text-5xl" {...props} />
ol: (props: any) => <ol className="list-decimal" {...props} />
...//etc
}
Then in [slug].tsx
pass components
as a prop:
...
import { components } from '../../components/MDXComponents';
...
export default function PostPage({ meta, code }: Post) {
const Component = React.useMemo(() => getMDXComponent(code), [code]);
return (
<Layout>
<Container>
<Header />
<article className="mb-32">
<Head>
<title>
{meta.title} | {WEBSITE_SHORT_URL}
</title>
</Head>
<PostHeader
title={meta.title}
coverImage={meta.coverImage}
date={meta.publishedAt}
/>
<div className="max-w-4xl mx-auto">
<Component
components={components} // <-- components passed here
/>
</div>
</article>
</Container>
</Layout>
);
}
Upvotes: 2