وضع المسودة (Draft Mode)

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

يحتوي Next.js على ميزة تسمى وضع المسودة (Draft Mode) التي تحل هذه المشكلة. فيما يلي إرشادات حول كيفية استخدامها.

الخطوة 1: إنشاء معالج المسار والوصول إليه

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

ثم، استورد draftMode من next/headers واستدعِ طريقة enable().

// معالج المسار لتفعيل وضع المسودة
import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  draftMode().enable()
  return new Response('تم تفعيل وضع المسودة')
}
// معالج المسار لتفعيل وضع المسودة
import { draftMode } from 'next/headers'

export async function GET(request) {
  draftMode().enable()
  return new Response('تم تفعيل وضع المسودة')
}

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

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

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

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

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

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

ثانيًا، إذا كان نظام إدارة المحتوى الخاص بك يدعم تعيين عناوين URL مخصصة للمسودات، فحدد ما يلي كعنوان URL للمسودة. يفترض هذا أن معالج المسار موجود في app/api/draft/route.ts

Terminal
https://<your-site>/api/draft?secret=<token>&slug=<path>
  • <your-site> يجب أن يكون نطاق النشر الخاص بك.
  • <token> يجب استبداله بالرمز السري الذي قمت بتوليده.
  • <path> يجب أن يكون المسار للصفحة التي تريد معاينتها. إذا كنت تريد معاينة /posts/foo، فيجب استخدام &slug=/posts/foo.

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

أخيرًا، في معالج المسار:

  • تحقق من تطابق السر وأن معلمة 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('رمز غير صالح', { status: 401 })
  }

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

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

  // تفعيل وضع المسودة عن طريق تعيين الكوكي
  draftMode().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('رمز غير صالح', { status: 401 })
  }

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

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

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

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

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

الخطوة 2: تحديث الصفحة

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

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

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

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

async function getData() {
  const { isEnabled } = 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 } = 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 للمسودة على نظام إدارة المحتوى الخاص بك أو قم بالوصول يدويًا، ويجب أن تتمكن من رؤية المسودة.

Terminal
https://<your-site>/api/draft?secret=<token>&slug=<path>

المزيد من التفاصيل

مسح كوكي وضع المسودة

افتراضيًا، تنتهي جلسة وضع المسودة عند إغلاق المتصفح.

لمسح كوكي وضع المسودة يدويًا، قم بإنشاء معالج مسار يستدعي draftMode().disable():

import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  draftMode().disable()
  return new Response('تم تعطيل وضع المسودة')
}
import { draftMode } from 'next/headers'

export async function GET(request) {
  draftMode().disable()
  return new Response('تم تعطيل وضع المسودة')
}

ثم، أرسل طلبًا إلى /api/disable-draft لاستدعاء معالج المسار. إذا كنت تستدعي هذا المسار باستخدام next/link، فيجب عليك تمرير prefetch={false} لمنع حذف الكوكي عن طريق الخطأ أثناء الجلب المسبق.

فريد لكل next build

سيتم إنشاء قيمة جديدة لكوكي الالتفاف في كل مرة تقوم فيها بتشغيل next build.

هذا يضمن أنه لا يمكن تخمين كوكي الالتفاف.

معلومة مفيدة: لاختبار وضع المسودة محليًا عبر HTTP، سيحتاج متصفحك إلى السماح بملفات تعريف الارتباط من طرف ثالث والوصول إلى التخزين المحلي.