كيفية معاينة المحتوى باستخدام وضع المسودة (Draft Mode) في Next.js

يسمح لك وضع المسودة (Draft Mode) بمعاينة محتوى المسودة من نظام إدارة المحتوى (CMS) في تطبيق Next.js الخاص بك. هذا مفيد للصفحات الثابتة التي يتم إنشاؤها أثناء عملية البناء حيث يسمح لك بالتبديل إلى التصيير الديناميكي ورؤية التغييرات في المسودة دون الحاجة إلى إعادة بناء موقعك بالكامل.

تشرح هذه الصفحة كيفية تمكين واستخدام وضع المسودة.

الخطوة 1: إنشاء معالج المسار (Route Handler)

قم بإنشاء معالج المسار (Route Handler). يمكن أن يكون له أي اسم، على سبيل المثال، app/api/draft/route.ts.

export async function GET(request: Request) {
  return new Response('')
}
export async function GET() {
  return new Response('')
}

ثم، قم باستيراد دالة draftMode واستدعاء طريقة enable().

import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  const draft = await draftMode()
  draft.enable()
  return new Response('Draft mode is enabled')
}
import { draftMode } from 'next/headers'

export async function GET(request) {
  const draft = await draftMode()
  draft.enable()
  return new Response('Draft mode is enabled')
}

سيؤدي هذا إلى تعيين كوكي (cookie) لتمكين وضع المسودة. الطلبات اللاحقة التي تحتوي على هذا الكوكي ستؤدي إلى تفعيل وضع المسودة وتغيير سلوك الصفحات التي تم إنشاؤها بشكل ثابت.

يمكنك اختبار هذا يدويًا عن طريق زيارة /api/draft والنظر في أدوات المطور في المتصفح. لاحظ رأس الاستجابة Set-Cookie مع كوكي باسم __prerender_bypass.

الخطوة 2: الوصول إلى معالج المسار من نظام إدارة المحتوى (CMS)

تفترض هذه الخطوات أن نظام إدارة المحتوى الذي تستخدمه يدعم تعيين عنوان URL مخصص للمسودة. إذا لم يكن كذلك، فلا يزال بإمكانك استخدام هذه الطريقة لتأمين عناوين URL للمسودة، ولكنك ستحتاج إلى إنشاء عنوان URL للمسودة والوصول إليه يدويًا. ستختلف الخطوات المحددة اعتمادًا على نظام إدارة المحتوى الذي تستخدمه.

للوصول الآمن إلى معالج المسار من نظام إدارة المحتوى الخاص بك:

  1. قم بإنشاء رمز سري (secret token string) باستخدام أداة إنشاء الرموز التي تختارها. سيكون هذا الرمز السري معروفًا فقط لتطبيق Next.js الخاص بك ونظام إدارة المحتوى.
  2. إذا كان نظام إدارة المحتوى الخاص بك يدعم تعيين عناوين URL مخصصة للمسودة، فحدد عنوان URL للمسودة (يفترض هذا أن معالج المسار موجود في app/api/draft/route.ts). على سبيل المثال:
Terminal
https://<your-site>/api/draft?secret=<token>&slug=<path>
  • <your-site> يجب أن يكون نطاق النشر الخاص بك.
  • <token> يجب استبداله بالرمز السري الذي أنشأته.
  • <path> يجب أن يكون المسار للصفحة التي تريد عرضها. إذا كنت تريد عرض /posts/one، فيجب استخدام &slug=/posts/one.

قد يسمح لك نظام إدارة المحتوى بتضمين متغير في عنوان URL للمسودة بحيث يمكن تعيين <path> ديناميكيًا بناءً على بيانات نظام إدارة المحتوى مثل: &slug=/posts/{entry.fields.slug}

  1. في معالج المسار الخاص بك، تحقق من تطابق الرمز السري ووجود معلمة slug (إذا لم تكن موجودة، يجب أن يفشل الطلب)، استدعاء draftMode.enable() لتعيين الكوكي. ثم، قم بتوجيه المتصفح إلى المسار المحدد بواسطة slug:
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  // تحليل معلمات سلسلة الاستعلام
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const slug = searchParams.get('slug')

  // التحقق من الرمز السري والمعلمات التالية
  // يجب أن يكون هذا الرمز السري معروفًا فقط لهذا المعالج المسار ونظام إدارة المحتوى
  if (secret !== 'MY_SECRET_TOKEN' || !slug) {
    return new Response('Invalid token', { status: 401 })
  }

  // جلب نظام إدارة المحتوى للتحقق من وجود `slug` المقدم
  // ستنفذ getPostBySlug المنطق المطلوب لجلب البيانات من نظام إدارة المحتوى
  const post = await getPostBySlug(slug)

  // إذا لم يكن slug موجودًا، منع تمكين وضع المسودة
  if (!post) {
    return new Response('Invalid slug', { status: 401 })
  }

  // تمكين وضع المسودة عن طريق تعيين الكوكي
  const draft = await draftMode()
  draft.enable()

  // توجيه إلى المسار من المنشور الذي تم جلب بياناته
  // لا نقوم بالتوجيه إلى searchParams.slug لأن ذلك قد يؤدي إلى ثغرات إعادة توجيه مفتوحة
  redirect(post.slug)
}
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request) {
  // تحليل معلمات سلسلة الاستعلام
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const slug = searchParams.get('slug')

  // التحقق من الرمز السري والمعلمات التالية
  // يجب أن يكون هذا الرمز السري معروفًا فقط لهذا المعالج المسار ونظام إدارة المحتوى
  if (secret !== 'MY_SECRET_TOKEN' || !slug) {
    return new Response('Invalid token', { status: 401 })
  }

  // جلب نظام إدارة المحتوى للتحقق من وجود `slug` المقدم
  // ستنفذ getPostBySlug المنطق المطلوب لجلب البيانات من نظام إدارة المحتوى
  const post = await getPostBySlug(slug)

  // إذا لم يكن slug موجودًا، منع تمكين وضع المسودة
  if (!post) {
    return new Response('Invalid slug', { status: 401 })
  }

  // تمكين وضع المسودة عن طريق تعيين الكوكي
  const draft = await draftMode()
  draft.enable()

  // توجيه إلى المسار من المنشور الذي تم جلب بياناته
  // لا نقوم بالتوجيه إلى searchParams.slug لأن ذلك قد يؤدي إلى ثغرات إعادة توجيه مفتوحة
  redirect(post.slug)
}

إذا نجح ذلك، فسيتم توجيه المتصفح إلى المسار الذي تريد عرضه مع كوكي وضع المسودة.

الخطوة 3: معاينة محتوى المسودة

الخطوة التالية هي تحديث صفحتك للتحقق من قيمة draftMode().isEnabled.

إذا طلبت صفحة تحتوي على الكوكي المعين، فسيتم جلب البيانات في وقت الطلب (بدلاً من وقت البناء).

علاوة على ذلك، ستكون قيمة isEnabled true.

// صفحة تقوم بجلب البيانات
import { draftMode } from 'next/headers'

async function getData() {
  const { isEnabled } = await draftMode()

  const url = isEnabled
    ? 'https://draft.example.com'
    : 'https://production.example.com'

  const res = await fetch(url)

  return res.json()
}

export default async function Page() {
  const { title, desc } = await getData()

  return (
    <main>
      <h1>{title}</h1>
      <p>{desc}</p>
    </main>
  )
}
// صفحة تقوم بجلب البيانات
import { draftMode } from 'next/headers'

async function getData() {
  const { isEnabled } = await draftMode()

  const url = isEnabled
    ? 'https://draft.example.com'
    : 'https://production.example.com'

  const res = await fetch(url)

  return res.json()
}

export default async function Page() {
  const { title, desc } = await getData()

  return (
    <main>
      <h1>{title}</h1>
      <p>{desc}</p>
    </main>
  )
}

إذا قمت بالوصول إلى معالج مسار المسودة (مع secret و slug) من نظام إدارة المحتوى الخاص بك أو يدويًا باستخدام عنوان URL، فيجب أن تكون قادرًا الآن على رؤية محتوى المسودة. وإذا قمت بتحديث مسودتك دون نشر، فيجب أن تكون قادرًا على معاينة المسودة.