Einführung/Anleitungen/MDX

Verwendung von Markdown und MDX in Next.js

Markdown ist eine leichtgewichtige Auszeichnungssprache zur Textformatierung. Sie ermöglicht es Ihnen, mit einfacher Text-Syntax zu schreiben und diese in strukturell valides HTML umzuwandeln. Sie wird häufig für das Schreiben von Inhalten auf Websites und Blogs verwendet.

Sie schreiben...

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

Ausgabe:

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

MDX ist eine Erweiterung von Markdown, die es Ihnen ermöglicht, JSX direkt in Ihren Markdown-Dateien zu schreiben. Es ist eine leistungsstarke Möglichkeit, dynamische Interaktivität hinzuzufügen und React-Komponenten in Ihre Inhalte einzubetten.

Next.js kann sowohl lokale MDX-Inhalte innerhalb Ihrer Anwendung als auch remote MDX-Dateien unterstützen, die dynamisch auf dem Server abgerufen werden. Das Next.js-Plugin übernimmt die Transformation von Markdown und React-Komponenten in HTML, einschließlich der Unterstützung für die Verwendung in Server Components (Standard im App Router).

Gut zu wissen: Sehen Sie sich die Portfolio Starter Kit-Vorlage für ein vollständiges funktionierendes Beispiel an.

Abhängigkeiten installieren

Das @next/mdx-Paket und verwandte Pakete werden verwendet, um Next.js so zu konfigurieren, dass es Markdown und MDX verarbeiten kann. Es bezieht Daten aus lokalen Dateien, sodass Sie Seiten mit der Erweiterung .md oder .mdx direkt in Ihrem /pages- oder /app-Verzeichnis erstellen können.

Installieren Sie diese Pakete, um MDX mit Next.js zu rendern:

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

next.config.mjs konfigurieren

Aktualisieren Sie die next.config.mjs-Datei im Stammverzeichnis Ihres Projekts, um die Verwendung von MDX zu konfigurieren:

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

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Konfigurieren Sie `pageExtensions`, um Markdown- und MDX-Dateien einzuschließen
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
  // Optional: Fügen Sie hier weitere Next.js-Konfigurationen hinzu
}

const withMDX = createMDX({
  // Fügen Sie hier gewünschte Markdown-Plugins hinzu
})

// MDX-Konfiguration mit Next.js-Konfiguration zusammenführen
export default withMDX(nextConfig)

Dies ermöglicht es .mdx-Dateien, als Seiten, Routen oder Importe in Ihrer Anwendung zu fungieren.

Behandlung von .md-Dateien

Standardmäßig kompiliert next/mdx nur Dateien mit der Erweiterung .mdx. Um .md-Dateien mit webpack zu behandeln, aktualisieren Sie die extension-Option:

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

Gut zu wissen: Turbopack unterstützt derzeit die extension-Option nicht und daher auch keine .md-Dateien.

Eine mdx-components.tsx-Datei hinzufügen

Erstellen Sie eine mdx-components.tsx (oder .js)-Datei im Stammverzeichnis Ihres Projekts, um globale MDX-Komponenten zu definieren. Zum Beispiel auf derselben Ebene wie pages oder app oder innerhalb von src, falls zutreffend.

import type { MDXComponents } from 'mdx/types'

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

Gut zu wissen:

MDX rendern

Sie können MDX mit Next.js' dateibasiertem Routing oder durch Importieren von MDX-Dateien in andere Seiten rendern.

Dateibasiertes Routing verwenden

Bei der Verwendung von dateibasiertem Routing können Sie MDX-Seiten wie jede andere Seite verwenden.

In App Router-Apps schließt dies die Möglichkeit ein, Metadaten zu verwenden.

Erstellen Sie eine neue MDX-Seite im /app-Verzeichnis:

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

Sie können MDX in diesen Dateien verwenden und sogar React-Komponenten direkt in Ihrer MDX-Seite importieren:

import { MyComponent } from 'my-component'

# Willkommen auf meiner MDX-Seite!

Dies ist ein **fetter** und _kursiver_ Text.

Dies ist eine Liste in Markdown:

- Eins
- Zwei
- Drei

Schauen Sie sich meine React-Komponente an:

<MyComponent />

Das Navigieren zur /mdx-page-Route sollte Ihre gerenderte MDX-Seite anzeigen.

Importe verwenden

Erstellen Sie eine neue Seite im /app-Verzeichnis und eine MDX-Datei an einem beliebigen Ort:

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

Sie können MDX in diesen Dateien verwenden und sogar React-Komponenten direkt in Ihrer MDX-Seite importieren:

import { MyComponent } from 'my-component'

# Willkommen auf meiner MDX-Seite!

Dies ist ein **fetter** und _kursiver_ Text.

Dies ist eine Liste in Markdown:

- Eins
- Zwei
- Drei

Schauen Sie sich meine React-Komponente an:

<MyComponent />

Importieren Sie die MDX-Datei in die Seite, um den Inhalt anzuzeigen:

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

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

Das Navigieren zur /mdx-page-Route sollte Ihre gerenderte MDX-Seite anzeigen.

Dynamische Importe verwenden

Sie können dynamische MDX-Komponenten importieren, anstatt das Dateisystem-Routing für MDX-Dateien zu verwenden.

Sie können beispielsweise ein dynamisches Routensegment haben, das MDX-Komponenten aus einem separaten Verzeichnis lädt:

Routensegmente für dynamische MDX-Komponenten

generateStaticParams kann verwendet werden, um die bereitgestellten Routen vorab zu rendern. Durch die Markierung von dynamicParams als false führt der Zugriff auf eine nicht in generateStaticParams definierte Route zu einem 404-Fehler.

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

Gut zu wissen: Stellen Sie sicher, dass Sie die .mdx-Dateierweiterung in Ihrem Import angeben. Während es nicht erforderlich ist, Modulpfad-Aliase (z.B. @/content) zu verwenden, vereinfacht dies Ihren Importpfad.

Benutzerdefinierte Stile und Komponenten verwenden

Markdown wird beim Rendern auf native HTML-Elemente abgebildet. Zum Beispiel generiert das Schreiben des folgenden Markdowns:

## Dies ist eine Überschrift

Dies ist eine Liste in Markdown:

- Eins
- Zwei
- Drei

Das folgende HTML:

<h2>Dies ist eine Überschrift</h2>

<p>Dies ist eine Liste in Markdown:</p>

<ul>
  <li>Eins</li>
  <li>Zwei</li>
  <li>Drei</li>
</ul>

Um Ihren Markdown zu gestalten, können Sie benutzerdefinierte Komponenten bereitstellen, die auf die generierten HTML-Elemente abgebildet werden. Stile und Komponenten können global, lokal und mit gemeinsamen Layouts implementiert werden.

Globale Stile und Komponenten

Das Hinzufügen von Stilen und Komponenten in mdx-components.tsx betrifft alle MDX-Dateien in Ihrer Anwendung.

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

// Diese Datei ermöglicht es Ihnen, benutzerdefinierte React-Komponenten
// für die Verwendung in MDX-Dateien bereitzustellen. Sie können jede
// React-Komponente importieren und verwenden, einschließlich Inline-Stilen,
// Komponenten aus anderen Bibliotheken und mehr.

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    // Ermöglicht die Anpassung eingebauter Komponenten, z.B. zum Hinzufügen von Stilen.
    h1: ({ children }) => (
      <h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
    ),
    img: (props) => (
      <Image
        sizes="100vw"
        style={{ width: '100%', height: 'auto' }}
        {...(props as ImageProps)}
      />
    ),
    ...components,
  }
}

Lokale Stile und Komponenten

Sie können lokale Stile und Komponenten auf bestimmte Seiten anwenden, indem Sie sie in importierte MDX-Komponenten übergeben. Diese werden mit globalen Stilen und Komponenten zusammengeführt und überschreiben diese.

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} />
}

Gemeinsame Layouts

Um ein Layout über mehrere MDX-Seiten hinweg zu teilen, können Sie die integrierte Layout-Unterstützung mit dem App Router verwenden.

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // Erstellen Sie hier gemeinsame Layouts oder Stile
  return <div style={{ color: 'blue' }}>{children}</div>
}

Verwendung des Tailwind-Typografie-Plugins

Wenn Sie Tailwind zur Gestaltung Ihrer Anwendung verwenden, ermöglicht Ihnen das Plugin @tailwindcss/typography, Ihre Tailwind-Konfiguration und -Stile in Ihren Markdown-Dateien wiederzuverwenden.

Das Plugin fügt eine Reihe von prose-Klassen hinzu, mit denen Sie typografische Stile auf Inhaltsblöcke anwenden können, die aus Quellen wie Markdown stammen.

Installieren Sie Tailwind-Typografie und verwenden Sie es mit gemeinsamen Layouts, um die gewünschte prose-Klasse hinzuzufügen.

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // Erstellen Sie hier gemeinsame Layouts oder Stile
  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

Frontmatter ist eine YAML-ähnliche Schlüssel/Wert-Paarung, die verwendet werden kann, um Daten über eine Seite zu speichern. @next/mdx unterstützt Frontmatter nicht standardmäßig, aber es gibt viele Lösungen, um Frontmatter zu Ihrem MDX-Inhalt hinzuzufügen, wie z.B.:

@next/mdx erlaubt Ihnen jedoch, Exporte wie bei jeder anderen JavaScript-Komponente zu verwenden:

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

# Blogbeitrag

Die Metadaten können nun außerhalb der MDX-Datei referenziert werden:

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

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

Ein häufiger Anwendungsfall hierfür ist, wenn Sie über eine Sammlung von MDX-Dateien iterieren und Daten extrahieren möchten. Zum Beispiel das Erstellen einer Blog-Indexseite aus allen Blogbeiträgen. Sie können Pakete wie Node's fs-Modul oder globby verwenden, um ein Verzeichnis mit Beiträgen zu lesen und die Metadaten zu extrahieren.

Gut zu wissen:

  • Die Verwendung von fs, globby usw. ist nur serverseitig möglich.
  • Sehen Sie sich die Portfolio Starter Kit-Vorlage für ein vollständiges funktionierendes Beispiel an.

remark- und rehype-Plugins

Sie können optional remark- und rehype-Plugins bereitstellen, um den MDX-Inhalt zu transformieren.

Beispielsweise können Sie remark-gfm verwenden, um GitHub Flavored Markdown zu unterstützen.

Da das remark- und rehype-Ökosystem nur ESM unterstützt, müssen Sie next.config.mjs oder next.config.ts als Konfigurationsdatei verwenden.

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

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Erlaube .mdx-Erweiterungen für Dateien
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
  // Optional: Fügen Sie hier weitere Next.js-Konfigurationen hinzu
}

const withMDX = createMDX({
  // Fügen Sie hier gewünschte Markdown-Plugins hinzu
  options: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
  },
})

// Kombinieren Sie MDX- und Next.js-Konfiguration
export default withMDX(nextConfig)

Verwendung von Plugins mit Turbopack

Um Plugins mit Turbopack zu verwenden, aktualisieren Sie auf die neueste Version von @next/mdx und geben Sie Plugin-Namen als Zeichenkette an:

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)

Gut zu wissen:

remark- und rehype-Plugins ohne serialisierbare Optionen können noch nicht mit Turbopack verwendet werden, aufgrund der Unfähigkeit, JavaScript-Funktionen an Rust zu übergeben

Remote-MDX

Wenn Ihre MDX-Dateien oder -Inhalte an einem anderen Ort gespeichert sind, können Sie sie dynamisch auf dem Server abrufen. Dies ist nützlich für Inhalte, die in einem CMS, einer Datenbank oder anderswo gespeichert sind. Ein Community-Paket für diesen Anwendungsfall ist next-mdx-remote-client.

Gut zu wissen: Bitte gehen Sie vorsichtig vor. MDX wird in JavaScript kompiliert und auf dem Server ausgeführt. Sie sollten MDX-Inhalte nur aus einer vertrauenswürdigen Quelle abrufen, da dies sonst zu Remote-Code-Ausführung (RCE) führen kann.

Das folgende Beispiel verwendet next-mdx-remote-client:

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

export default async function RemoteMdxPage() {
  // MDX-Text - kann aus einer Datenbank, einem CMS, einem Fetch-Aufruf oder anderswo stammen...
  const res = await fetch('https://...')
  const markdown = await res.text()
  return <MDXRemote source={markdown} />
}

Das Navigieren zur Route /mdx-page-remote sollte Ihren gerenderten MDX-Inhalt anzeigen.

Vertiefung: Wie wird Markdown in HTML umgewandelt?

React versteht Markdown nicht nativ. Der Markdown-Text muss zunächst in HTML umgewandelt werden. Dies kann mit remark und rehype erreicht werden.

remark ist ein Ökosystem von Tools rund um Markdown. rehype ist das gleiche, aber für HTML. Der folgende Codeausschnitt wandelt beispielsweise Markdown in HTML um:

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) // In Markdown-AST umwandeln
    .use(remarkRehype) // In HTML-AST transformieren
    .use(rehypeSanitize) // HTML-Eingabe bereinigen
    .use(rehypeStringify) // AST in serialisiertes HTML umwandeln
    .process('Hello, Next.js!')

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

Das remark- und rehype-Ökosystem enthält Plugins für Syntax-Hervorhebung, verlinkte Überschriften, Generierung eines Inhaltsverzeichnisses und mehr.

Wenn Sie @next/mdx wie oben gezeigt verwenden, müssen Sie remark oder rehype nicht direkt verwenden, da dies für Sie erledigt wird. Wir beschreiben es hier, um ein tieferes Verständnis dafür zu vermitteln, was das @next/mdx-Paket im Hintergrund tut.

Verwendung des Rust-basierten MDX-Compilers (experimentell)

Next.js unterstützt einen neuen MDX-Compiler, der in Rust geschrieben wurde. Dieser Compiler ist noch experimentell und wird nicht für den Produktionseinsatz empfohlen. Um den neuen Compiler zu verwenden, müssen Sie next.config.js konfigurieren, wenn Sie es an withMDX übergeben:

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

mdxRs akzeptiert auch ein Objekt, um die Transformation von MDX-Dateien zu konfigurieren.

next.config.js
module.exports = withMDX({
  experimental: {
    mdxRs: {
      jsxRuntime?: string            // Benutzerdefinierte JSX-Runtime
      jsxImportSource?: string       // Benutzerdefinierte JSX-Importquelle,
      mdxType?: 'gfm' | 'commonmark' // Konfiguriert, welche Art von MDX-Syntax zum Parsen & Transformieren verwendet wird
    },
  },
})