
Markdown
Markdown is a standardised way of writing documents to include formatting like headers, italics, bold, list, links, etc.
Normally, there is not a mechanism within frameworks to directly translate markdown into rendered HTML. This guide may help you add this functionality to a Next.js project should you wish, like us, to add blogs to your site.
Markdown itself is a lightweight markup language. You won't normally see the untranslated raw code, unless you are perhaps a Content Creator. You will normally see the rendered HTML result in a browser.
An example of markdown might look like this:
Code: Markdown## This is a header
**This is bold**
_And this is italic_
[I am a link](https://futools.online)
- [ ] Task List Item 1
- [ ] Task List Item 2
- [ ] Task List Item 3
Which will render to this:
-----start example-----
This is a header
This is bold
And this is italic
- Task List Item 1
- Task List Item 2
- Task List Item 3
-----end example-----
We have created an example page.mdx
file, written using just markdown. It demonstrates a variety of markdown syntax.
To see it, just click Show me all the markdown.
If you are familiar with markdown you may have noticed we have tweaked our rendering of markdown, so it may look slightly different from what you are use to, but the basics are still the same as other markdown renderings.
For more information and a thorough guide on markdown, we recommend visiting the Markdown Guide page.
What you will need to know
This article assumes the reader is a developer that knows their way around Markdown, TypeScript, React.js, and [Next.js] https://nextjs.org/). Familiarity with Tailwind-css would also be useful.
For this guide it is advised to use Next.js version 15 upwards and React.js version 18 upwards. We can not ensure that your installation will work properly if you don't use this configuration.
We have also assumed that you are using the App Router configuration for Next.
Installation
You will need to install the @next/mdx
package, and related packages, with this install command:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
Also, it may help to read the Next.js guide: How to use markdown and MDX in Next.js to understand more.
We chose to install the remark-gfm plugin. So that we could support the GitHub Flavored Markdown Spec. This allows for autolink literals, footnotes, strikethrough, tables, task lists.
This is optional. To add remark-gfm
use the install command:
npm install remark-gfm
Next.js
You will need to add the mdx imports to your next.config.ts
file. It should look something like this:
Code: TypeScriptFilename: next.config.tsimport remarkGfm from "remark-gfm";
import createMDX from "@next/mdx";
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// Configure `pageExtensions` to include markdown and MDX files
pageExtensions: ["md", "mdx", "ts", "tsx"],
};
const withMDX = createMDX({
// Add markdown plugins here, as desired
extension: /\.(md|mdx)$/,
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
});
// Merge MDX config with Next.js config
export default withMDX(nextConfig);
If you do not wish to use the 'remark-gfm' dependencies, just remove the line: import remarkGfm from "remark-gfm";
and the options
property block.
MDX Component
You will need to create a mdx-component.tsx
file. This will need to be in the root folder, i.e. the same level as your /app
folder. In the case of futools, we have the /app
folder as a subfolder of the /src
folder, which is becoming the standard.
For a better understanding, it may be helpful to read the How to use markdown and MDX in Next.js - Add an mdx-components.tsx file section.
A mdx-component.tsx
file looks something like this at the bare minimum:
Code: TSXFilename: mdx-component.tsximport type { MDXComponents } from "mdx/types";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
};
}
However, you may want to spruce it up so your final translated markdown doesn't look so bland.
The mdx-components.tsx
file used at futools looks something like this (we have removed some code to avoid confusion):
Code: TSXFilename: mdx-components.tsximport type { MDXComponents } from "mdx/types";
import { ComponentPropsWithoutRef } from "react";
import Link from "next/link";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
h1: ({ children, ...props }: ComponentPropsWithoutRef<"h1">) => (
<h1 className="mb-2 mt-5" {...props}>
{children}
</h1>
),
h2: ({ children, ...props }: ComponentPropsWithoutRef<"h2">) => (
<h2 className="mb-2 mt-5" {...props}>
{children}
</h2>
),
h3: ({ children, ...props }: ComponentPropsWithoutRef<"h3">) => (
<h3 className="mb-2 mt-5" {...props}>
{children}
</h3>
),
h4: ({ children, ...props }: ComponentPropsWithoutRef<"h4">) => (
<h4 className="mb-2 mt-5" {...props}>
{children}
</h4>
),
h5: ({ children, ...props }: ComponentPropsWithoutRef<"h5">) => (
<h5 className="mb-2 mt-5" {...props}>
{children}
</h5>
),
h6: ({ children, ...props }: ComponentPropsWithoutRef<"h6">) => (
<h6 className="mb-2 mt-5" {...props}>
{children}
</h6>
),
p: (props: ComponentPropsWithoutRef<"p">) => (
<p className="mb-2 leading-snug" {...props} />
),
ol: (props: ComponentPropsWithoutRef<"ol">) => (
<ol
className="space-y-1"
style={{
listStyleType: "revert",
listStyle: "revert",
padding: "revert",
}}
{...props}
/>
),
ul: (props: ComponentPropsWithoutRef<"ul">) => {
if (props.className === "contains-task-list") {
return <ul className="space-y-1" {...props} />;
}
return (
<ul
className="space-y-1"
style={{
listStyleType: "revert",
listStyle: "revert",
padding: "revert",
}}
{...props}
/>
);
},
a: ({ href, children, ...props }: ComponentPropsWithoutRef<"a">) => {
const className = " inline-link";
if (href?.startsWith("/")) {
return (
<Link href={href} className={className} {...props}>
{children}
</Link>
);
}
if (href?.startsWith("#")) {
return (
<a href={href} className={className} {...props}>
{children}
</a>
);
}
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className={className}
{...props}
>
{children}
</a>
);
},
table: ({ children, ...props }: ComponentPropsWithoutRef<"table">) => (
<span>
<table {...props}>{children}</table>
</span>
),
tr: ({ children, ...props }: ComponentPropsWithoutRef<"tr">) => (
<tr
{...props}
className="odd:bg-white even:bg-gray-100 odd:dark:bg-gray-900 even:dark:bg-gray-800"
>
{children}
</tr>
),
th: ({ children, ...props }: ComponentPropsWithoutRef<"th">) => (
<th
{...props}
className="border border-gray-300 p-2 dark:border-gray-700"
>
{children}
</th>
),
td: ({ children, ...props }: ComponentPropsWithoutRef<"td">) => (
<td
{...props}
className="border border-gray-300 p-2 dark:border-gray-700"
>
{children}
</td>
),
blockquote: (props: ComponentPropsWithoutRef<"blockquote">) => (
<blockquote
className={
"ml-[0.075em] border-l-4 " +
"border-gray-300 pl-4 text-gray-700 dark:border-zinc-600 dark:text-zinc-300"
}
{...props}
/>
),
pre: ({ children, ...props }: ComponentPropsWithoutRef<"pre">) => (
<pre {...props} className="mb-2">
{children}
</pre>
),
...components,
};
}
As you can see, for each tag you can override the default and render the HTML as you desire.
Tailwind
If you are using tailwind-css, you may need to add the new mdx-component.tsx
file path to your tailwind.config.ts
file so tailwind can compile it accordingly.
The tailwind.config.ts
file used at futools looks something like this (again, code has been removed to avoid confusion):
Code: TypeScriptFilename: tailwind.config.tsimport type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/mdx-components.tsx",
],
};
export default config;
My First Markdown Page
Now you should be good to go. Now you can add a page.md
or page.mdx
file somewhere under your /app
folder.
For example, you could make a 'mdx-test' folder: /app/mdx-test
and add a file called: page.md
with this content:
Code: MarkdownFilename: page.md## This is a header
**This is bold**
_And this is italic_
[I am a link](https://futools.online)
- [ ] Task List Item 1
- [ ] Task List Item 2
- [ ] Task List Item 3
Now goto your site to something like `http://locolhost/mdx-test' to see your rendered markdown page (we don't know your setup so this URL is just a guess 🙂).
Conclusion
The Next.js team and many others have done a lot to make adding markdown much easier, but as you can see there is still a bit of technical shenanigans needed.
We hope you enjoyed this article. We will try to keep you informed and entertained.
Please come back again and get blogging!