While we believe that a strong separation of data and display is important, we understand that formats that mix the two such as MDX (Markdown with embedded JSX components) have become a popular and powerful authoring format for developers.
Remix supports using MDX in two ways:
.mdx
file as one of your route modulesimport
a .mdx
file into one of your route modules (in app/routes
)The simplest way to get started with MDX in Remix is to create a route module. Just like .js
and .ts
files in your app/routes
directory, .mdx
(and .md
) files will participate in automatic file system based routing.
MDX routes allow you to define both meta and headers as if they were a code based route:
---
meta:
title: My First Post
description: Isn't this awesome?
headers:
Cache-Control: no-cache
---
# Hello Content!
The lines in the document above between the ---
are called "frontmatter". You can think of them like metadata for your document, formatted as YAML.
You can reference your frontmatter fields through the global attributes
variable in your MDX:
---
componentData:
label: Hello, World!
---
import SomeComponent from "~/components/some-component";
# Hello MDX!
<SomeComponent {...attributes.componentData} />
By creating a app/routes/posts/first-post.mdx
we can start writing a blog post:
---
meta:
title: My First Post
description: Isn't this just awesome?
---
# Example Markdown Post
You can reference your frontmatter data through "attributes". The title of this post is {attributes.meta.title}!
Besides just route level MDX, you can also import these files anywhere yourself as if it were a regular JavaScript module.
When you import
a .mdx
file, the exports of the module are:
import Component, {
attributes,
filename
} from "./first-post.mdx";
The following example demonstrates how you might build a simple blog with MDX, including individual pages for the posts themselves and an index page that shows all posts.
In app/routes/index.jsx
:
import { useRouteData } from "remix";
import { Link } from "react-router-dom";
// Import all your posts from the app/routes/posts directory. Since these are
// regular route modules, they will all be available for individual viewing
// at /posts/a, for example.
import * as postA from "./posts/a.mdx";
import * as postB from "./posts/b.md";
import * as postC from "./posts/c.md";
function postFromModule(mod) {
return {
slug: mod.filename.replace(/\.mdx?$/, ""),
...mod.attributes.meta
};
}
export function loader() {
// Return metadata about each of the posts for display on the index page.
// Referencing the posts here instead of in the Index component down below
// lets us avoid bundling the actual posts themselves in the bundle for the
// index page.
return [
postFromModule(postA),
postFromModule(postB),
postFromModule(postC)
];
}
export default function Index() {
let posts = useRouteData();
return (
<ul>
{posts.map(post => (
<li key={post.slug}>
<Link to={post.slug}>{post.title}</Link>
{post.description && <p>{post.description}</p>}
</li>
))}
</ul>
);
}
If you wish to configure your own remark plugins you can do so through the remix.config.js
's mdx
export:
const {
remarkMdxFrontmatter
} = require("remark-mdx-frontmatter");
// can be an sync / async function or an object
exports.mdx = async filename => {
const [rehypeHighlight, remarkToc] = await Promise.all([
import("rehype-highlight").then(mod => mod.default),
import("remark-toc").then(mod => mod.default)
]);
return {
remarkPlugins: [remarkToc],
rehypePlugins: [rehypeHighlight]
};
};