كيفية استخدام Markdown و MDX في Next.js

Markdown هي لغة ترميز خفيفة الوزن تُستخدم لتنسيق النص. تتيح لك الكتابة باستخدام تركيب نص عادي وتحويله إلى HTML صحيح هيكليًا. تُستخدم عادةً لكتابة المحتوى على المواقع والمدونات.

تكتب...

أنا **أحب** استخدام [Next.js](https://nextjs.org/)

النتيجة:

<p>أنا <strong>أحب</strong> استخدام <a href="https://nextjs.org/">Next.js</a></p>

MDX هي مجموعة شاملة من Markdown تتيح لك كتابة JSX مباشرة في ملفات Markdown الخاصة بك. إنها طريقة قوية لإضافة تفاعل ديناميكي وتضمين مكونات React داخل محتواك.

يدعم Next.js كلًا من محتوى MDX المحلي داخل تطبيقك، وملفات MDX البعيدة التي يتم جلبها ديناميكيًا على الخادم. يتعامل ملحق Next.js مع تحويل Markdown ومكونات React إلى HTML، بما في ذلك الدعم للاستخدام في مكونات الخادم (الافتراضي في موجه التطبيق).

جيد أن تعرف: شاهد Portfolio Starter Kit للحصول على مثال عمل كامل.

تثبيت التبعيات

يُستخدم حزمة @next/mdx والحزم المرتبطة بها لتكوين Next.js حتى يتمكن من معالجة Markdown وMDX. إنها تستورد البيانات من الملفات المحلية، مما يسمح لك بإنشاء صفحات بامتداد .md أو .mdx، مباشرة في دليل /pages أو /app.

قم بتثبيت هذه الحزم لعرض MDX مع Next.js:

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

تكوين next.config.mjs

قم بتحديث ملف next.config.mjs في جذر مشروعك لتكوينه لاستخدام MDX:

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

/** @type {import('next').NextConfig} */
const nextConfig = {
  // تكوين `pageExtensions` لتشمل ملفات markdown و MDX
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
  // اختياريًا، أضف أي تكوين آخر لـ Next.js أدناه
}

const withMDX = createMDX({
  // أضف إضافات markdown هنا، كما تريد
})

// دمج تكوين MDX مع تكوين Next.js
export default withMDX(nextConfig)

يسمح هذا لملفات .mdx بالعمل كصفحات أو مسارات أو استيرادات في تطبيقك.

معالجة ملفات .md

افتراضيًا، يقوم next/mdx بتحويل الملفات ذات الامتداد .mdx فقط. لمعالجة ملفات .md باستخدام webpack، قم بتحديث خيار extension:

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

جيد أن تعرف: Turbopack لا يدعم حاليًا خيار extension وبالتالي لا يدعم ملفات .md.

إضافة ملف mdx-components.tsx

قم بإنشاء ملف mdx-components.tsx (أو .js) في جذر مشروعك لتحديد مكونات MDX العالمية. على سبيل المثال، في نفس مستوى pages أو app، أو داخل src إذا كان ذلك ينطبق.

import type { MDXComponents } from 'mdx/types'

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

جيد أن تعرف:

عرض MDX

يمكنك عرض MDX باستخدام توجيه الملفات في Next.js أو عن طريق استيراد ملفات MDX إلى صفحات أخرى.

استخدام توجيه الملفات

عند استخدام توجيه الملفات، يمكنك استخدام صفحات MDX مثل أي صفحة أخرى.

في تطبيقات موجه التطبيق، يتضمن ذلك القدرة على استخدام البيانات الوصفية.

قم بإنشاء صفحة MDX جديدة داخل دليل /app:

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

يمكنك استخدام MDX في هذه الملفات، وحتى استيراد مكونات React، مباشرة داخل صفحة MDX الخاصة بك:

import { MyComponent } from 'my-component'

# مرحبًا بكم في صفحة MDX الخاصة بي!

هذا بعض النص **عريض** و *مائل*.

هذه قائمة في markdown:

- واحد
- اثنان
- ثلاثة

تحقق من مكون React الخاص بي:

<MyComponent />

يجب أن يؤدي التنقل إلى مسار /mdx-page إلى عرض صفحة MDX المقدمة الخاصة بك.

استخدام الاستيرادات

قم بإنشاء صفحة جديدة داخل دليل /app وملف MDX في أي مكان تريده:

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

يمكنك استخدام MDX في هذه الملفات، وحتى استيراد مكونات React، مباشرة داخل صفحة MDX الخاصة بك:

import { MyComponent } from 'my-component'

# مرحبًا بكم في صفحة MDX الخاصة بي!

هذا بعض النص **عريض** و *مائل*.

هذه قائمة في markdown:

- واحد
- اثنان
- ثلاثة

تحقق من مكون React الخاص بي:

<MyComponent />

قم باستيراد ملف MDX داخل الصفحة لعرض المحتوى:

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

export default function Page() {
  return <Welcome />
}
import Welcome from '@/markdown/welcome.mdx'

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

يجب أن يؤدي التنقل إلى مسار /mdx-page إلى عرض صفحة MDX المقدمة الخاصة بك.

استخدام الاستيرادات الديناميكية

يمكنك استيراد مكونات MDX ديناميكيًا بدلاً من استخدام توجيه نظام الملفات لملفات MDX.

على سبيل المثال، يمكنك الحصول على مقطع مسار ديناميكي يقوم بتحميل مكونات MDX من دليل منفصل:

مقاطع المسار لمكونات MDX الديناميكية

يمكن استخدام generateStaticParams لتصيير المسارات المقدمة مسبقًا. عن طريق تعيين dynamicParams على false، سيؤدي الوصول إلى مسار غير محدد في generateStaticParams إلى ظهور خطأ 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
export default async function Page({ params }) {
  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

جيد أن تعرف: تأكد من تحديد امتداد ملف .mdx في استيرادك. بينما ليس مطلوبًا استخدام أسماء مسارات الوحدات (مثل @/content)، فإنه يبسط مسار الاستيراد الخاص بك.

استخدام الأنماط والمكونات المخصصة

عند تقديم Markdown، يتم تعيينه إلى عناصر HTML الأصلية. على سبيل المثال، كتابة Markdown التالية:

## هذا عنوان

هذه قائمة في markdown:

- واحد
- اثنان
- ثلاثة

يولد HTML التالي:

<h2>هذا عنوان</h2>

<p>هذه قائمة في markdown:</p>

<ul>
  <li>واحد</li>
  <li>اثنان</li>
  <li>ثلاثة</li>
</ul>

لتنسيق Markdown الخاص بك، يمكنك تقديم مكونات مخصصة تُعرض على عناصر HTML المولدة. يمكن تطبيق الأنماط والمكونات عالميًا ومحليًا ومع تخطيطات مشتركة.

الأنماط والمكونات العالمية

إضافة الأنماط والمكونات في mdx-components.tsx سيؤثر على جميع ملفات MDX في تطبيقك.

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

// يسمح لك هذا الملف بتقديم مكونات React مخصصة
// لاستخدامها في ملفات MDX. يمكنك استيراد واستخدام أي
// مكون React تريده، بما في ذلك الأنماط المضمنة،
// مكونات من مكتبات أخرى، والمزيد.

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    // يسمح بتخصيص المكونات المدمجة، على سبيل المثال لإضافة تنسيق.
    h1: ({ children }) => (
      <h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
    ),
    img: (props) => (
      <Image
        sizes="100vw"
        style={{ width: '100%', height: 'auto' }}
        {...(props as ImageProps)}
      />
    ),
    ...components,
  }
}
import Image from 'next/image'

// يسمح لك هذا الملف بتقديم مكونات React مخصصة
// لاستخدامها في ملفات MDX. يمكنك استيراد واستخدام أي
// مكون React تريده، بما في ذلك الأنماط المضمنة،
// مكونات من مكتبات أخرى، والمزيد.

export function useMDXComponents(components) {
  return {
    // يسمح بتخصيص المكونات المدمجة، على سبيل المثال لإضافة تنسيق.
    h1: ({ children }) => (
      <h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
    ),
    img: (props) => (
      <Image
        sizes="100vw"
        style={{ width: '100%', height: 'auto' }}
        {...props}
      />
    ),
    ...components,
  }
}

الأنماط والمكونات المحلية

يمكنك تطبيق الأنماط والمكونات المحلية على صفحات محددة عن طريق تمريرها إلى مكونات MDX المستوردة. سوف تدمج هذه وتتجاوز الأنماط والمكونات العالمية.

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

التخطيطات المشتركة

لمشاركة تخطيط عبر صفحات MDX، يمكنك استخدام دعم التخطيط المدمج مع موجه التطبيق.

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // أنشئ أي تخطيط أو أنماط مشتركة هنا
  return <div style={{ color: 'blue' }}>{children}</div>
}
export default function MdxLayout({ children }) {
  // أنشئ أي تخطيط أو أنماط مشتركة هنا
  return <div style={{ color: 'blue' }}>{children}</div>
}

استخدام إضافة Tailwind للنصوص

إذا كنت تستخدم Tailwind لتنسيق تطبيقك، فإن استخدام إضافة @tailwindcss/typography سيتيح لك إعادة استخدام إعدادات وتنسيقات Tailwind في ملفات الماركداون الخاصة بك.

تضيف الإضافة مجموعة من فئات prose التي يمكن استخدامها لإضافة تنسيقات نصية إلى كتل المحتوى القادمة من مصادر مثل الماركداون.

قم بتثبيت إضافة Tailwind للنصوص واستخدمها مع التنسيقات المشتركة لإضافة فئة prose التي تريدها.

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // يمكنك إنشاء أي تنسيقات أو أنماط مشتركة هنا
  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>
  )
}
export default function MdxLayout({ children }) {
  // يمكنك إنشاء أي تنسيقات أو أنماط مشتركة هنا
  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)

البيانات الأولية هي أزواج مفتاح/قيمة تشبه YAML يمكن استخدامها لتخزين بيانات حول صفحة. @next/mdx لا يدعم البيانات الأولية افتراضيًا، رغم وجود العديد من الحلول لإضافتها إلى محتوى MDX الخاص بك، مثل:

@next/mdx يسمح لك باستخدام التصديرات مثل أي مكون JavaScript آخر:

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

# منشور المدونة

يمكن الآن الرجوع إلى البيانات الوصفية خارج ملف MDX:

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

export default function Page() {
  console.log('metadata: ', metadata)
  //=> { author: 'John Doe' }
  return <BlogPost />
}
import BlogPost, { metadata } from '@/content/blog-post.mdx'

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

إحدى حالات الاستخدام الشائعة لهذا هي عندما تريد التكرار عبر مجموعة من ملفات MDX واستخراج البيانات. على سبيل المثال، إنشاء صفحة فهرس للمدونة من جميع منشورات المدونة. يمكنك استخدام حزم مثل وحدة fs في Node أو globby لقراءة دليل المنشورات واستخراج البيانات الوصفية.

معلومة مفيدة:

  • استخدام fs، globby، إلخ. يمكن استخدامه فقط من جانب الخادم.
  • يمكنك عرض Portfolio Starter Kit للحصول على مثال عمل كامل.

إضافات remark و rehype

يمكنك اختياريًا توفير إضافات remark و rehype لتحويل محتوى MDX.

على سبيل المثال، يمكنك استخدام remark-gfm لدعم GitHub Flavored Markdown.

نظرًا لأن نظام remark و rehype يعمل فقط مع ESM، ستحتاج إلى استخدام next.config.mjs أو next.config.ts كملف التكوين.

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

/** @type {import('next').NextConfig} */
const nextConfig = {
  // السماح بامتدادات .mdx للملفات
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
  // اختياريًا، أضف أي تكوين آخر لـ Next.js أدناه
}

const withMDX = createMDX({
  // أضف إضافات الماركداون هنا، كما تريد
  options: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
  },
})

// اجمع تكوين MDX و Next.js
export default withMDX(nextConfig)

استخدام الإضافات مع Turbopack

لاستخدام الإضافات مع Turbopack، قم بالترقية إلى أحدث إصدار من @next/mdx وحدد أسماء الإضافات باستخدام سلسلة نصية:

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)

معلومة مفيدة:

لا يمكن استخدام إضافات remark و rehype التي لا تحتوي على خيارات قابلة للتسلسل بعد مع Turbopack، بسبب عدم القدرة على تمرير دوال JavaScript إلى Rust

MDX عن بُعد

إذا كانت ملفات أو محتوى MDX الخاص بك موجود في مكان آخر، يمكنك جلبها ديناميكيًا من الخادم. هذا مفيد للمحتوى المخزن في نظام إدارة المحتوى (CMS)، قاعدة بيانات، أو أي مكان آخر. أحد الحزم المجتمعية لهذا الغرض هو next-mdx-remote-client.

معلومة مفيدة: يرجى المتابعة بحذر. يتم تجميع MDX إلى JavaScript وتنفيذها على الخادم. يجب عليك فقط جلب محتوى MDX من مصدر موثوق، وإلا يمكن أن يؤدي هذا إلى تنفيذ كود عن بُعد (RCE).

المثال التالي يستخدم next-mdx-remote-client:

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

export default async function RemoteMdxPage() {
  // نص MDX - يمكن أن يكون من قاعدة بيانات، نظام إدارة محتوى، جلب، أي مكان...
  const res = await fetch('https://...')
  const markdown = await res.text()
  return <MDXRemote source={markdown} />
}
import { MDXRemote } from 'next-mdx-remote-client/rsc'

export default async function RemoteMdxPage() {
  // نص MDX - يمكن أن يكون من قاعدة بيانات، نظام إدارة محتوى، جلب، أي مكان...
  const res = await fetch('https://...')
  const markdown = await res.text()
  return <MDXRemote source={markdown} />
}

التنقل إلى مسار /mdx-page-remote يجب أن يعرض MDX المقدم الخاص بك.

الغوص العميق: كيف يتم تحويل الماركداون إلى HTML؟

React لا يفهم الماركداون بشكل طبيعي. يحتاج نص الماركداون العادي إلى التحويل أولاً إلى HTML. يمكن تحقيق هذا باستخدام remark و rehype.

remark هو نظام أدوات حول الماركداون. rehype هو نفسه، ولكن لـ HTML. على سبيل المثال، مقتطف الكود التالي يحول الماركداون إلى 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) // التحويل إلى شجرة الماركداون
    .use(remarkRehype) // التحويل إلى شجرة HTML
    .use(rehypeSanitize) // تعقيم إدخال HTML
    .use(rehypeStringify) // تحويل الشجرة إلى HTML متسلسل
    .process('Hello, Next.js!')

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

يحتوي نظام remark و rehype على إضافات لـ تلوين الصيغة، ربط العناوين، إنشاء جدول محتويات، والمزيد.

عند استخدام @next/mdx كما هو موضح أعلاه، لا تحتاج إلى استخدام remark أو rehype مباشرة، حيث يتم التعامل معها لك. نحن نصفها هنا لفهم أعمق لما يفعله حزمة @next/mdx تحتها.

استخدام مترجم MDX المبني على Rust (تجريبي)

يدعم Next.js مترجم MDX جديد مكتوب بلغة Rust. هذا المترجم لا يزال تجريبيًا ولا يوصى باستخدامه في الإنتاج. لاستخدام المترجم الجديد، تحتاج إلى تكوين next.config.js عند تمريره إلى withMDX:

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

يقبل mdxRs أيضًا كائنًا لتكوين كيفية تحويل ملفات mdx.

next.config.js
module.exports = withMDX({
  experimental: {
    mdxRs: {
      jsxRuntime?: string            // وقت تشغيل jsx مخصص
      jsxImportSource?: string       // مصدر استيراد jsx مخصص،
      mdxType?: 'gfm' | 'commonmark' // تكوين نوع صيغة mdx التي سيتم استخدامها للتحليل والتحويل
    },
  },
})

روابط مفيدة