Markdown allows text content to be written with implicit formatting that nearly matches how one would write text without thought of formatting. A lot of web content is also simply text content; a lot of web content is built on the ReactJS library; and NextJS has become a popular framework, extending ReactJS. With that, I was motivated to use the Markdown content of my NextJS web app, but I had trouble simply relying on their documentation, so I thought I would document what I got working.
- Use Markdown for a text-heavy portion of a page of my web app, and
- To be able to have an entire page of content come from a single Markdown page
The former is a feature of NextJS to allow markdown content to be imported as a “component,” which can be rendered like any component in ReactJs.
The latter would rely NextJS’s App Router that assumes many URL paths for the app parallel the file/directory hierarchy of the app itself. So, merely placing a “page” file in the app’s directory will create a new URL path for the website. That means that you can create a “page.md” or “page.mdx” file with Markdown text content to define the content for that URL. .mdx files also allow JSX syntax so that React/Next components can be embedded within the Markdown.
Setup
The first issue was getting things set up. Here were the steps that worked for me.
- Add packages. The highlighted line was missing from the documentation.
npm install @mdx-js/loader @mdx-js/react @next/mdx npm install -D @types/mdx
- Update
next.config.ts
from:import type {NextConfig} from 'next'; const nextConfig: NextConfig = { typescript: { ignoreBuildErrors: true, }, eslint: { ignoreDuringBuilds: true, }, }; export default nextConfig;
Add
import withMDX from "@next/mdx";
- Add “md” (optional) and “mdx” extensions for App Router support:
nextConfig.pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx']
- Create, then wrap the Next config with mdx-config
withMDX = createMDX({})
…export default withMDX(nextConfig)
to get:
import type {NextConfig} from 'next'; import withMDX from "@next/mdx"; const nextConfig: NextConfig = { pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"], typescript: { ignoreBuildErrors: true, }, eslint: { ignoreDuringBuilds: true, }, }; const withMdxConfig = withMDX({ options: { remarkPlugins: [], rehypePlugins: [], // providerImportSource: "@mdx-js/react", // To use `MDXProvider`, uncomment this line }, }); export default withMdxConfig(nextConfig);
- Add an mdx-components.tsx file to
src/
. See mdx-components.js for more details.import type { MDXComponents } from 'mdx/types' export function useMDXComponents(components: MDXComponents): MDXComponents { return { ...components, } }
- Help VSCode resolve imports
- Open settings
- Search for “file associations”
- Add item key =
*.mdx
, value =markdown-react
- Styles will need to be defined for all the generated HTML tags. I’m still playing with this, but there are three approaches
- wrapper()
className
definitions- Import a style sheet
Defining Markdown Output Style
Markdown syntax is translated to HTML. By default, the HTML tags have had their formatting stripped, for some reason. That means that the tags need to have style/class definitions applied to render properly. See How to use markdown and MDX in Next.js, for the official word.
The following are tags that correspond to Markdown syntax:
Markdown | HTML | |
---|---|---|
Headings | # Level 1## Level 2### Level 3… ###### Level 6 |
<h1> Level 1</h1> <h2> Level 2</h2> <h3> Level 3</h3> … <h6> Level 6</h6> |
Bold, Italic, Bold Italic | * and _ are interchangeable** bold** or __ bold__ * italic* or _ italic_ *** bold-italic*** |
<b> bold</b> <i> italic</i> <b><i> bold-italic</i></b> or <i><b> bold-italic</b></i> |
Bullet list | - Item 1- Item 2 |
<ul> <li> Item 1</li> <li> Item 2</li> </ul> |
Ordered list | 1. Item 12. Item 2 |
<ol> <li> Item 1</li> <li> Item 2</li> </ol> |
Unformatted |
|
|
Link | [ description]( http://…) |
<a href=" http://…"> description</a> |
Plain or paragraph text | text … | <p> text …</p> |
Wrapper
import type { MDXComponents } from 'mdx/types' export function useMDXComponents(components: MDXComponents): MDXComponents { return { ...components, wrapper: ({ children }) => <div style={{ ... }}>{children}</div>, } }
Import Style Sheet
This is the easiest solution, but with the least containment and control. The problem is that it affects tags on the whole page. Stay tuned as I figure out whether this can be contained. This can be added to the mdx-components.tsx or within the mdx file itself with import ./mdxstyle.css
(or what/where-ever you define this file)
/* This is a more aggressive reset, consider if you need it */ /* * { all: unset; display: revert; // Revert display to its default based on tag } */ h1, h2, h3, h4, h5, h6 { all: unset; /* Reset all styles */ display: block; /* Ensure headings are block elements */ font-weight: bold; /* Make headings bold */ margin-inline-start: 0; /* Reset left margin */ margin-inline-end: 0; /* Reset right margin */ } h1 { display: block; font-weight: bold; margin-inline-start: 0; margin-inline-end: 0; font-size: 2em; } h2 { display: block; font-weight: bold; margin-inline-start: 0; margin-inline-end: 0; font-size: 1.5em; } h3 { display: block; font-weight: bold; margin-inline-start: 0; margin-inline-end: 0; font-size: 1.17em; } h4 { display: block; font-weight: bold; margin-inline-start: 0; margin-inline-end: 0; } h5 { display: block; font-weight: bold; margin-inline-start: 0; margin-inline-end: 0; font-size: .83em; } h6 { display: block; font-weight: bold; margin-inline-start: 0; margin-inline-end: 0; font-size: .67em; } p { display: block; margin-inline-start: 0; margin-inline-end: 0; } a { all: unset; color: -webkit-link; /* Restore default link color */ text-decoration: underline; /* Restore default underline */ cursor: pointer; /* Restore default cursor */ } ul { display: block; list-style-type: disc; padding-inline-start: 40px; } ol { display: block; list-style-type: decimal; padding-inline-start: 40px } li { display: list-item; } blockquote { display: block; margin-left: 40px; margin-right: 40px; } code { font-family: monospace; /* Restore default monospace font */ } pre { display: block; font-family: monospace; margin-top: 1em; margin-bottom: 1em; white-space: pre; /* Restore default pre-formatted text handling */ } img { all: unset; display: inline-block; /* Images are inline-block by default */ max-width: 100%; /* A common and helpful default for responsive images */ height: auto; /* Maintain aspect ratio */ } /* Add resets for other tags as needed */
import type { MDXComponents } from 'mdx/types' import './resets.css'; export function useMDXComponents(components: MDXComponents): MDXComponents { return { ...components, } }
Now,
- App Router will render
page.mdx
files. - NextJS modules can render mdx file as components:
// src/app/updates/page.tsx import UpdateContent from "./updates.mdx" export default async function UpdatesPage() { return ( <> <h1>Updated News</h1> <UpdateContent/> </> )
Note that:
- [ ]
checkboxes do not work- Markdown tables do not work
Catching the Technology Train, After I’d Disembarked
Staying away from programming for too long, things change faster than I’d ever expected. Even with a strong foundation, when things move ahead so quickly, too many things change and it isn’t as easy to hop back on the train, as I would have thought. Today, I am trying to catch up with technology train that is web development. How did I become so disconnected?
The result is that, not only do I need to learn new languages and their nuances, I even need to learn a new workflow, a new way of doing programming. While I am not starting from scratch, a lot of my prior “expertise” is not directly applicable; so it feels like starting from scratch.
I could just stick to the old technologies that I am still expert at, but that isn’t cutting edge. Still I could do that. Then I wouldn’t have anything to be stressed about. And, that wouldn’t be so bad, now would it?
Posted in Commentary