astro/packages/integrations/mdx
2022-10-19 11:39:26 -04:00
..
src wip: add ?astro-asset-ssr flag 2022-10-19 11:39:26 -04:00
test P5: fix MDX memory leak (#4939) 2022-09-30 17:13:44 -05:00
CHANGELOG.md [ci] release (#4957) 2022-10-03 11:52:23 -04:00
package.json [ci] release (#4957) 2022-10-03 11:52:23 -04:00
README.md docs: Update references to support channel in Discord. (#4872) 2022-09-26 21:02:05 +08:00
tsconfig.json MDX support (#3706) 2022-06-30 13:09:09 -05:00

@astrojs/mdx 📝

This Astro integration enables the usage of MDX components and allows you to create pages as .mdx files.

Why MDX?

MDX is the defacto solution for embedding components, such as interactive charts or alerts, within Markdown content. If you have existing content authored in MDX, this integration makes migrating to Astro a breeze.

Want to learn more about MDX before using this integration?
Check out “What is MDX?”, a deep-dive on the MDX format.

Installation

Quick Install

The astro add command-line tool automates the installation for you. Run one of the following commands in a new terminal window. (If you aren't sure which package manager you're using, run the first command.) Then, follow the prompts, and type "y" in the terminal (meaning "yes") for each one.

# Using NPM
npx astro add mdx
# Using Yarn
yarn astro add mdx
# Using PNPM
pnpm astro add mdx

If you run into any issues, feel free to report them to us on GitHub and try the manual installation steps below.

Manual Install

First, install the @astrojs/mdx package using your package manager. If you're using npm or aren't sure, run this in the terminal:

npm install @astrojs/mdx

Then, apply this integration to your astro.config.* file using the integrations property:

astro.config.mjs

import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';

export default defineConfig({
  // ...
  integrations: [mdx()],
});

Finally, restart the dev server.

Editor Integration

VS Code supports Markdown by default. However, for MDX editor support, you may wish to add the following setting in your VSCode config. This ensures authoring MDX files provides a Markdown-like editor experience.

"files.associations": {
    "*.mdx": "markdown"
}

Usage

You can add MDX pages to your project by adding .mdx files within your src/pages/ directory.

Components

To use components in your MDX pages in Astro, head to our UI framework documentation. You'll explore:

  • 📦 how framework components are loaded,
  • 💧 client-side hydration options, and
  • 🤝 opportunities to mix and nest frameworks together

Client Directives are still required in .mdx files.

Note

: .mdx files adhere to strict JSX syntax rather than Astro's HTML-like syntax.

Variables

MDX supports export statements to add variables to your templates. These variables are accessible both from the template itself and as named properties when importing the template somewhere else.

For instance, you can export a title field from an MDX page or component to use as a heading with {JSX expressions}:

export const title = 'My first MDX post'

# {title}

This title will be accessible from import and glob statements as well:

---
// src/pages/index.astro
const posts = await Astro.glob('./*.mdx');
---

{posts.map(post => <p>{post.title}</p>)}

See the official "how MDX works" guide for more on MDX variables.

Exported properties

Alongside your MDX variable exports, we generate a few helpful exports as well. These are accessible when importing an MDX file via import statements or Astro.glob.

file

The absolute path to the MDX file (e.g. home/user/projects/.../file.md).

url

The browser-ready URL for MDX files under src/pages/. For example, src/pages/en/about.mdx will provide a url of /en/about/. For MDX files outside of src/pages, url will be undefined.

getHeadings()

Returns: { depth: number; slug: string; text: string }[]

A function that returns an array of all headings (i.e. h1 -> h6 elements) in the MDX file. Each headings slug corresponds to the generated ID for a given heading and can be used for anchor links.

Frontmatter

Astro also supports YAML-based frontmatter out-of-the-box. By default, all variables declared in a frontmatter fence (---) will be accessible via the frontmatter export.

For example, we can add a title and publishDate to an MDX page or component like so:

---
title: 'My first MDX post'
publishDate: '21 September 2022'
---

# {frontmatter.title}

Now, this title and publishDate will be accessible from import and glob statements via the frontmatter property. This matches the behavior of plain markdown in Astro as well!

---
// src/pages/index.astro
const posts = await Astro.glob('./*.mdx');
---

{posts.map(post => (
  <Fragment>
    <h2>{post.frontmatter.title}</h2>
    <time>{post.frontmatter.publishDate}</time>
  </Fragment>
))}

Inject frontmatter via remark or rehype plugins

You may want to inject frontmatter properties across all of your MDX files. By using a remark or rehype plugin, you can generate these properties based on a files contents.

You can append to the data.astro.frontmatter property from your plugins file argument like so:

// example-remark-plugin.mjs
export function exampleRemarkPlugin() {
  // All remark and rehype plugins return a separate function
  return function (tree, file) {
    file.data.astro.frontmatter.customProperty = 'Generated property';
  }
}

After applying this plugin to your MDX integration config:

// astro.config.mjs
import mdx from '@astrojs/mdx';
import { exampleRemarkPlugin } from './example-remark-plugin.mjs';

export default {
  integrations: [
    mdx({
      remarkPlugins: [exampleRemarkPlugin],
    }),
  ],
};

…every MDX file will have customProperty in its frontmatter! See our Markdown documentation for more usage instructions and a reading time plugin example.

Layouts

Layouts can be applied in the same way as standard Astro Markdown. You can add a layout to your frontmatter like so:

---
layout: '../layouts/BaseLayout.astro' 
title: 'My Blog Post'
---

Then, you can retrieve all other frontmatter properties from your layout via the frontmatter property, and render your MDX using the default <slot />. See layout props for a complete list of props available.

---
// src/layouts/BaseLayout.astro
const { frontmatter, url } = Astro.props;
---
<html>
  <head>
    <meta rel="canonical" href={new URL(url, Astro.site).pathname}>
    <title>{frontmatter.title}</title>
  </head>
  <body>
    <h1>{frontmatter.title}</h1>
    <!-- Rendered MDX will be passed into the default slot. -->
    <slot />
  </body>
</html>

You can set a layouts Props type with the MDXLayoutProps helper.

:::note MDXLayoutProps is the same as the MarkdownLayoutProps utility type with rawContent() and compiledContent() removed (since these are not available for .mdx files). Feel free to use MarkdownLayoutProps instead when sharing a layout across .md and .mdx files. :::

---
// src/layouts/BaseLayout.astro
import type { MDXLayoutProps } from 'astro';

type Props = MDXLayoutProps<{
  // Define frontmatter props here
  title: string;
  author: string;
  date: string;
}>;

// Now, `frontmatter`, `url`, and other MDX layout properties
// are accessible with type safety
const { frontmatter, url } = Astro.props;
---
<html>
  <head>
    <meta rel="canonical" href={new URL(url, Astro.site).pathname}>
    <title>{frontmatter.title}</title>
  </head>
  <body>
    <h1>{frontmatter.title}</h1>
    <slot />
  </body>
</html>

Layout props

All exported properties are available from Astro.props in your layout, with two key differences:

  • Heading information (i.e. h1 -> h6 elements) is available via the headings array, rather than a getHeadings() function.
  • file and url are also available as nested frontmatter properties (i.e. frontmatter.url and frontmatter.file). This is consistent with Astro's Markdown layout properties.

Astro recommends using the MDXLayoutProps type (see previous section) to explore all available properties.

Importing layouts manually

You may need to pass information to your layouts that does not (or cannot) exist in your frontmatter. In this case, you can import and use a <Layout /> component like any other component:

---
// src/pages/posts/first-post.mdx

title: 'My first MDX post'
publishDate: '21 September 2022'
---
import BaseLayout from '../layouts/BaseLayout.astro';

function fancyJsHelper() {
  return "Try doing that with YAML!";
}

<BaseLayout title={frontmatter.title} fancyJsHelper={fancyJsHelper}>
  Welcome to my new Astro blog, using MDX!
</BaseLayout>

Then, your values are available to you through Astro.props in your layout, and your MDX content will be injected into the page where your <slot /> component is written:

---
// src/layouts/BaseLayout.astro
const { title, fancyJsHelper } = Astro.props;
---
<!-- -->
<h1>{title}</h1>
<slot />
<p>{fancyJsHelper()}</p>
<!-- -->

Custom components

Under the hood, MDX will convert Markdown into HTML components. For example, this blockquote:

> A blockquote with *some* emphasis.

will be converted into this HTML:

<blockquote>
  <p>A blockquote with <em>some</em> emphasis.</p>
</blockquote>

But what if you want to specify your own markup for these blockquotes? In the above example, you could create a custom <Blockquote /> component (in any language) that either has a <slot /> component or accepts a children prop.

<blockquote class="bg-blue-50 p-4">
  <span class="text-4xl text-blue-600 mb-2">“</span>
  <slot />
</blockquote>

Then in the MDX file you import the component and export it to the components export.

import Blockquote from '../components/Blockquote.astro';
export const components = { blockquote: Blockquote };

Now, writing the standard Markdown blockquote syntax (>) will use your custom <Blockquote /> component instead. No need to use a component in Markdown, or write a remark/rehype plugin! Visit the MDX website for a full list of HTML elements that can be overwritten as custom components.

Custom components with imported mdx

When rendering imported MDX content, custom components can also be passed via the components prop:

---
import Content from '../content.mdx';
import Heading from '../Heading.astro';
---

<Content components={{ h1: Heading }} />

Syntax highlighting

The MDX integration respects your project's markdown.syntaxHighlight configuration.

We will highlight your code blocks with Shiki by default. You can customize this highlighter using the markdown.shikiConfig option in your astro.config. For example, you can apply a different built-in theme like so:

// astro.config.mjs
export default {
  markdown: {
    shikiConfig: {
      theme: 'dracula',
    },
  },
  integrations: [mdx()],
}

Visit our Shiki configuration docs for more on using Shiki with Astro.

Switch to Prism

You can also use the Prism syntax highlighter by setting markdown.syntaxHighlight to 'prism' in your astro.config like so:

// astro.config.mjs
export default {
  markdown: {
    syntaxHighlight: 'prism',
  },
  integrations: [mdx()],
}

This applies a minimal Prism renderer with added support for astro code blocks. Visit our "Prism configuration" docs for more on using Prism with Astro.

Switch to a custom syntax highlighter

You may want to apply your own syntax highlighter too. If your highlighter offers a remark or rehype plugin, you can flip off our syntax highlighting by setting markdown.syntaxHighlight: false and wiring up your plugin. For example, say you want to apply Shiki Twoslash's remark plugin:

// astro.config.mjs
import shikiTwoslash from 'remark-shiki-twoslash';

export default {
  markdown: {
  syntaxHighlight: false,
  },
  integrations: [mdx({
    remarkPlugins: [shikiTwoslash, { /* Shiki Twoslash config */ }],
  })],

Configuration

remarkPlugins

Remark plugins allow you to extend your Markdown with new capabilities. This includes auto-generating a table of contents, applying accessible emoji labels, and more. We encourage you to browse awesome-remark for a full curated list!

This example applies the remark-toc plugin to .mdx files. To customize plugin inheritance from your Markdown config or Astro's defaults, see the extendPlugins option.

// astro.config.mjs
import remarkToc from 'remark-toc';

export default {
  integrations: [mdx({
    remarkPlugins: [remarkToc],
  })],
}

rehypePlugins

Rehype plugins allow you to transform the HTML that your Markdown generates. We encourage you to browse awesome-rehype for a full curated list of plugins!

We apply our own (non-removable) collect-headings plugin. This applies IDs to all headings (i.e. h1 -> h6) in your MDX files to link to headings via anchor tags.

This example applies the rehype-minify plugin to .mdx files. To customize plugin inheritance from your Markdown config or Astro's defaults, see the extendPlugins option.

// astro.config.mjs
import rehypeMinifyHtml from 'rehype-minify';

export default {
  integrations: [mdx({
    rehypePlugins: [rehypeMinifyHtml],
  })],
}

extendPlugins

Type: 'markdown' | 'astroDefaults' | false

Default: 'markdown'

markdown (default)

By default, Astro inherits all remark and rehype plugins from the markdown option in your Astro config. This also respects the markdown.extendDefaultPlugins option to extend Astro's defaults. Any additional plugins you apply in your MDX config will be applied after your configured Markdown plugins.

This example applies remark-toc to Markdown and MDX, and rehype-minify to MDX alone:

// astro.config.mjs
import remarkToc from 'remark-toc';
import rehypeMinify from 'rehype-minify';

export default {
  markdown: {
    // Applied to .md and .mdx files
    remarkPlugins: [remarkToc],
  },
  integrations: [mdx({
    // Applied to .mdx files only
    rehypePlugins: [rehypeMinify],
  })],
}

astroDefaults

You may only want to extend Astro's default plugins without inheriting your Markdown config. This example will apply the default GitHub-Flavored Markdown and Smartypants plugins alongside remark-toc:

// astro.config.mjs
import remarkToc from 'remark-toc';

export default {
  markdown: {
    remarkPlugins: [/** ignored */]
  },
  integrations: [mdx({
    remarkPlugins: [remarkToc],
    // Astro defaults applied
    extendPlugins: 'astroDefaults',
  })],
}

false

If you don't want to extend any plugins, set extendPlugins to false:

// astro.config.mjs
import remarkToc from 'remark-toc';

export default {
  integrations: [mdx({
    remarkPlugins: [remarkToc],
    // Astro defaults not applied
    extendPlugins: false,
  })],
}

Examples

Troubleshooting

For help, check out the #support channel on Discord. Our friendly Support Squad members are here to help!

You can also check our Astro Integration Documentation for more on integrations.

Contributing

This package is maintained by Astro's Core team. You're welcome to submit an issue or PR!

Changelog

See CHANGELOG.md for a history of changes to this integration.