Introduction/Guides/MDX

Comment utiliser Markdown et MDX dans Next.js

Markdown est un langage de balisage léger utilisé pour formater du texte. Il vous permet d'écrire en utilisant une syntaxe en texte brut et de la convertir en HTML structurellement valide. Il est couramment utilisé pour écrire du contenu sur des sites web et des blogs.

Vous écrivez...

I **love** using [Next.js](https://nextjs.org/)

Sortie :

<p>I <strong>love</strong> using <a href="https://nextjs.org/">Next.js</a></p>

MDX est un sur-ensemble de Markdown qui vous permet d'écrire du JSX directement dans vos fichiers Markdown. C'est une manière puissante d'ajouter de l'interactivité dynamique et d'intégrer des composants React dans votre contenu.

Next.js peut prendre en charge à la fois le contenu MDX local dans votre application et les fichiers MDX distants récupérés dynamiquement sur le serveur. Le plugin Next.js gère la transformation du Markdown et des composants React en HTML, y compris la prise en charge de leur utilisation dans les composants serveur (par défaut dans App Router).

Bon à savoir : Consultez le Portfolio Starter Kit pour un exemple complet fonctionnel.

Installer les dépendances

Le package @next/mdx, ainsi que les packages associés, sont utilisés pour configurer Next.js afin qu'il puisse traiter Markdown et MDX. Il récupère les données à partir de fichiers locaux, vous permettant de créer des pages avec une extension .md ou .mdx, directement dans vos répertoires /pages ou /app.

Installez ces packages pour afficher MDX avec Next.js :

Terminal
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

Configurer next.config.mjs

Mettez à jour le fichier next.config.mjs à la racine de votre projet pour le configurer afin d'utiliser MDX :

next.config.mjs
import createMDX from '@next/mdx'

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Configurez `pageExtensions` pour inclure les fichiers Markdown et MDX
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
  // Optionnellement, ajoutez toute autre configuration Next.js ci-dessous
}

const withMDX = createMDX({
  // Ajoutez ici les plugins Markdown souhaités
})

// Fusionnez la configuration MDX avec la configuration Next.js
export default withMDX(nextConfig)

Cela permet aux fichiers .mdx d'agir comme des pages, des routes ou des imports dans votre application.

Gestion des fichiers .md

Par défaut, next/mdx ne compile que les fichiers avec l'extension .mdx. Pour gérer les fichiers .md avec webpack, mettez à jour l'option extension :

next.config.mjs
const withMDX = createMDX({
  extension: /\.(md|mdx)$/,
})

Bon à savoir : Turbopack ne prend actuellement pas en charge l'option extension et ne supporte donc pas les fichiers .md.

Ajouter un fichier mdx-components.tsx

Créez un fichier mdx-components.tsx (ou .js) à la racine de votre projet pour définir les composants MDX globaux. Par exemple, au même niveau que pages ou app, ou dans src si applicable.

import type { MDXComponents } from 'mdx/types'

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    ...components,
  }
}

Bon à savoir :

Afficher MDX

Vous pouvez afficher MDX en utilisant le routage basé sur les fichiers de Next.js ou en important des fichiers MDX dans d'autres pages.

Utilisation du routage basé sur les fichiers

Lorsque vous utilisez le routage basé sur les fichiers, vous pouvez utiliser des pages MDX comme n'importe quelle autre page.

Dans les applications App Router, cela inclut la possibilité d'utiliser les métadonnées.

Créez une nouvelle page MDX dans le répertoire /app :

  my-project
  ├── app
  │   └── mdx-page
  │       └── page.(mdx/md)
  |── mdx-components.(tsx/js)
  └── package.json

Vous pouvez utiliser MDX dans ces fichiers, et même importer des composants React directement dans votre page MDX :

import { MyComponent } from 'my-component'

# Bienvenue sur ma page MDX !

Voici du texte en **gras** et en _italique_.

Voici une liste en Markdown :

- Un
- Deux
- Trois

Découvrez mon composant React :

<MyComponent />

Naviguer vers la route /mdx-page devrait afficher votre page MDX rendue.

Utilisation des imports

Créez une nouvelle page dans le répertoire /app et un fichier MDX où vous le souhaitez :

  .
  ├── app/
  │   └── mdx-page/
  │       └── page.(tsx/js)
  ├── markdown/
  │   └── welcome.(mdx/md)
  ├── mdx-components.(tsx/js)
  └── package.json

Vous pouvez utiliser MDX dans ces fichiers, et même importer des composants React directement dans votre page MDX :

import { MyComponent } from 'my-component'

# Bienvenue sur ma page MDX !

Voici du texte en **gras** et en _italique_.

Voici une liste en Markdown :

- Un
- Deux
- Trois

Découvrez mon composant React :

<MyComponent />

Importez le fichier MDX dans la page pour afficher le contenu :

import Welcome from '@/markdown/welcome.mdx'

export default function Page() {
  return <Welcome />
}

Naviguer vers la route /mdx-page devrait afficher votre page MDX rendue.

Utilisation des imports dynamiques

Vous pouvez importer des composants MDX dynamiques au lieu d'utiliser le routage par système de fichiers pour les fichiers MDX.

Par exemple, vous pouvez avoir un segment de route dynamique qui charge des composants MDX à partir d'un répertoire séparé :

Segments de route pour les composants MDX dynamiques

generateStaticParams peut être utilisé pour prérendre les routes fournies. En marquant dynamicParams comme false, l'accès à une route non définie dans generateStaticParams renverra une erreur 404.

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const { default: Post } = await import(`@/content/${slug}.mdx`)

  return <Post />
}

export function generateStaticParams() {
  return [{ slug: 'welcome' }, { slug: 'about' }]
}

export const dynamicParams = false

Bon à savoir : Assurez-vous de spécifier l'extension de fichier .mdx dans votre import. Bien qu'il ne soit pas nécessaire d'utiliser des alias de chemin de module (par exemple, @/content), cela simplifie votre chemin d'import.

Utiliser des styles et composants personnalisés

Markdown, lorsqu'il est rendu, correspond à des éléments HTML natifs. Par exemple, écrire le Markdown suivant :

## Ceci est un titre

Voici une liste en Markdown :

- Un
- Deux
- Trois

Génère le HTML suivant :

<h2>Ceci est un titre</h2>

<p>Voici une liste en Markdown :</p>

<ul>
  <li>Un</li>
  <li>Deux</li>
  <li>Trois</li>
</ul>

Pour styliser votre Markdown, vous pouvez fournir des composants personnalisés qui correspondent aux éléments HTML générés. Les styles et composants peuvent être implémentés globalement, localement et avec des mises en page partagées.

Styles et composants globaux

Ajouter des styles et composants dans mdx-components.tsx affectera tous les fichiers MDX de votre application.

import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } from 'next/image'

// Ce fichier vous permet de fournir des composants React personnalisés
// à utiliser dans les fichiers MDX. Vous pouvez importer et utiliser n'importe quel
// composant React que vous souhaitez, y compris des styles en ligne,
// des composants d'autres bibliothèques, et plus encore.

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    // Permet de personnaliser les composants intégrés, par exemple pour ajouter du style.
    h1: ({ children }) => (
      <h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
    ),
    img: (props) => (
      <Image
        sizes="100vw"
        style={{ width: '100%', height: 'auto' }}
        {...(props as ImageProps)}
      />
    ),
    ...components,
  }
}

Styles et composants locaux

Vous pouvez appliquer des styles et composants locaux à des pages spécifiques en les passant dans les composants MDX importés. Ceux-ci fusionneront avec et remplaceront les styles et composants globaux.

import Welcome from '@/markdown/welcome.mdx'

function CustomH1({ children }) {
  return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}

const overrideComponents = {
  h1: CustomH1,
}

export default function Page() {
  return <Welcome components={overrideComponents} />
}

Mises en page partagées

Pour partager une mise en page entre les pages MDX, vous pouvez utiliser la prise en charge intégrée des mises en page avec App Router.

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // Créez ici toute mise en page ou style partagé
  return <div style={{ color: 'blue' }}>{children}</div>
}

Utilisation du plugin Tailwind Typography

Si vous utilisez Tailwind pour styliser votre application, l'utilisation du plugin @tailwindcss/typography vous permettra de réutiliser votre configuration et vos styles Tailwind dans vos fichiers markdown.

Le plugin ajoute un ensemble de classes prose qui peuvent être utilisées pour ajouter des styles typographiques aux blocs de contenu provenant de sources comme le markdown.

Installez Tailwind Typography et utilisez-le avec les layouts partagés pour ajouter la classe prose souhaitée.

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // Créez ici tout layout ou style partagé
  return (
    <div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
      {children}
    </div>
  )
}

Frontmatter

Le frontmatter est une paire clé/valeur de type YAML qui peut être utilisée pour stocker des données sur une page. @next/mdx ne prend pas en charge le frontmatter par défaut, bien qu'il existe de nombreuses solutions pour l'ajouter à votre contenu MDX, telles que :

@next/mdx vous permet d'utiliser des exports comme n'importe quel autre composant JavaScript :

export const metadata = {
  author: 'John Doe',
}

# Article de blog

Les métadonnées peuvent maintenant être référencées en dehors du fichier MDX :

import BlogPost, { metadata } from '@/content/blog-post.mdx'

export default function Page() {
  console.log('metadata: ', metadata)
  //=> { author: 'John Doe' }
  return <BlogPost />
}

Un cas d'utilisation courant est lorsque vous souhaitez itérer sur une collection de MDX et extraire des données. Par exemple, créer une page d'index de blog à partir de tous les articles. Vous pouvez utiliser des packages comme le module fs de Node ou globby pour lire un répertoire d'articles et extraire les métadonnées.

Bon à savoir :

  • L'utilisation de fs, globby, etc. ne peut se faire que côté serveur.
  • Consultez le Portfolio Starter Kit pour un exemple complet.

Plugins remark et rehype

Vous pouvez éventuellement fournir des plugins remark et rehype pour transformer le contenu MDX.

Par exemple, vous pouvez utiliser remark-gfm pour prendre en charge GitHub Flavored Markdown.

Comme l'écosystème remark et rehype est uniquement ESM, vous devrez utiliser next.config.mjs ou next.config.ts comme fichier de configuration.

next.config.mjs
import remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Autoriser les extensions .mdx pour les fichiers
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
  // Optionnellement, ajoutez toute autre configuration Next.js ci-dessous
}

const withMDX = createMDX({
  // Ajoutez ici les plugins markdown souhaités
  options: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
  },
})

// Combinez la configuration MDX et Next.js
export default withMDX(nextConfig)

Utilisation des plugins avec Turbopack

Pour utiliser des plugins avec Turbopack, mettez à jour vers la dernière version de @next/mdx et spécifiez les noms des plugins sous forme de chaîne :

next.config.mjs
import createMDX from '@next/mdx'

/** @type {import('next').NextConfig} */
const nextConfig = {
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
}

const withMDX = createMDX({
  options: {
    remarkPlugins: [],
    rehypePlugins: [['rehype-katex', { strict: true, throwOnError: true }]],
  },
})

export default withMDX(nextConfig)

Bon à savoir :

Les plugins remark et rehype sans options sérialisables ne peuvent pas encore être utilisés avec Turbopack, en raison de l'impossibilité de passer des fonctions JavaScript à Rust

MDX distant

Si vos fichiers ou contenu MDX se trouvent ailleurs, vous pouvez les récupérer dynamiquement sur le serveur. Ceci est utile pour le contenu stocké dans un CMS, une base de données ou ailleurs. Un package communautaire pour ce cas d'utilisation est next-mdx-remote-client.

Bon à savoir : Procédez avec prudence. MDX est compilé en JavaScript et exécuté sur le serveur. Vous ne devez récupérer du contenu MDX que depuis une source de confiance, sinon cela peut conduire à une exécution de code à distance (RCE).

L'exemple suivant utilise next-mdx-remote-client :

import { MDXRemote } from 'next-mdx-remote-client/rsc'

export default async function RemoteMdxPage() {
  // Texte MDX - peut provenir d'une base de données, d'un CMS, d'une requête fetch, etc.
  const res = await fetch('https://...')
  const markdown = await res.text()
  return <MDXRemote source={markdown} />
}

Naviguer vers la route /mdx-page-remote devrait afficher votre MDX rendu.

Approfondissement : Comment transformer le markdown en HTML ?

React ne comprend pas nativement le markdown. Le texte brut markdown doit d'abord être transformé en HTML. Cela peut être accompli avec remark et rehype.

remark est un écosystème d'outils autour du markdown. rehype est similaire, mais pour le HTML. Par exemple, l'extrait de code suivant transforme le markdown en HTML :

import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'

main()

async function main() {
  const file = await unified()
    .use(remarkParse) // Convertit en AST markdown
    .use(remarkRehype) // Transforme en AST HTML
    .use(rehypeSanitize) // Nettoie l'entrée HTML
    .use(rehypeStringify) // Convertit l'AST en HTML sérialisé
    .process('Hello, Next.js!')

  console.log(String(file)) // <p>Hello, Next.js!</p>
}

L'écosystème remark et rehype contient des plugins pour la coloration syntaxique, les liens d'en-têtes, la génération d'une table des matières, et plus encore.

Lorsque vous utilisez @next/mdx comme montré ci-dessus, vous n'avez pas besoin d'utiliser remark ou rehype directement, car cela est géré pour vous. Nous le décrivons ici pour une meilleure compréhension de ce que fait le package @next/mdx en coulisses.

Utilisation du compilateur MDX basé sur Rust (expérimental)

Next.js prend en charge un nouveau compilateur MDX écrit en Rust. Ce compilateur est encore expérimental et n'est pas recommandé pour une utilisation en production. Pour utiliser le nouveau compilateur, vous devez configurer next.config.js lorsque vous le passez à withMDX :

next.config.js
module.exports = withMDX({
  experimental: {
    mdxRs: true,
  },
})

mdxRs accepte également un objet pour configurer la transformation des fichiers mdx.

next.config.js
module.exports = withMDX({
  experimental: {
    mdxRs: {
      jsxRuntime?: string            // Runtime JSX personnalisée
      jsxImportSource?: string       // Source d'import JSX personnalisée,
      mdxType?: 'gfm' | 'commonmark' // Configure le type de syntaxe MDX utilisé pour l'analyse et la transformation
    },
  },
})

Liens utiles