كيفية استخدام التصيير الجزئي المسبق (Partial Prerendering)

التصيير الجزئي المسبق (PPR) هو استراتيجية تصيير تتيح لك الجمع بين المحتوى الثابت والديناميكي في نفس المسار. هذا يحسن أداء الصفحة الأولي مع دعم البيانات الشخصية والديناميكية.

صفحة منتج مع تصيير جزئي مسبق تظهر التنقل الثابت ومعلومات المنتج، وعربة التسوق الديناميكية والمنتجات الموصى بها

عندما يزور المستخدم مسارًا:

  • يرسل الخادم هيكلًا يحتوي على المحتوى الثابت، مما يضمن تحميلًا أوليًا سريعًا.
  • يترك الهيكل فراغات للمحتوى الديناميكي الذي سيتم تحميله بشكل غير متزامن.
  • يتم بث الفراغات الديناميكية بالتوازي، مما يقلل من وقت التحميل الكلي للصفحة.

🎥 شاهد: لماذا PPR وكيف تعمل → يوتيوب (10 دقائق).

كيف يعمل التصيير الجزئي المسبق؟

لفهم التصيير الجزئي المسبق، من المفيد أن تكون على دراية باستراتيجيات التصيير المتاحة في Next.js.

التصيير الثابت (Static Rendering)

مع التصيير الثابت، يتم إنشاء HTML مسبقًا - إما في وقت البناء أو من خلال إعادة التحقق. يتم تخزين النتيجة ومشاركتها بين المستخدمين والطلبات.

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

التصيير الديناميكي (Dynamic Rendering)

مع التصيير الديناميكي، يتم إنشاء HTML في وقت الطلب. هذا يسمح لك بتقديم محتوى شخصي بناءً على بيانات وقت الطلب.

يصبح المكون ديناميكيًا إذا استخدم الواجهات البرمجية التالية:

في التصيير الجزئي المسبق، يؤدي استخدام هذه الواجهات البرمجية إلى حدوث خطأ React خاص يخبر Next.js أن المكون لا يمكن تصييره بشكل ثابت، مما يتسبب في حدوث خطأ في البناء. يمكنك استخدام حدود Suspense لتفادي تصيير المكون حتى وقت التشغيل.

Suspense

يستخدم React Suspense لتأجيل تصيير أجزاء من تطبيقك حتى يتم استيفاء شرط معين.

في التصيير الجزئي المسبق، يتم استخدام Suspense لتمييز الحدود الديناميكية في شجرة المكونات الخاصة بك.

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

لا يجعل تغليف مكون في Suspense المكون نفسه ديناميكيًا (استخدامك للواجهة البرمجية هو ما يفعله)، ولكن بدلاً من ذلك يتم استخدام Suspense كحدود تغلف المحتوى الديناميكي وتُمكّن البث.

app/page.js
import { Suspense } from 'react'
import StaticComponent from './StaticComponent'
import DynamicComponent from './DynamicComponent'
import Fallback from './Fallback'

export const experimental_ppr = true

export default function Page() {
  return (
    <>
      <StaticComponent />
      <Suspense fallback={<Fallback />}>
        <DynamicComponent />
      </Suspense>
    </>
  )
}

البث (Streaming)

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

مخطط يظهر صفحة مع تصيير جزئي على العميل، مع واجهة تحميل للأجزاء التي يتم بثها.

في التصيير الجزئي المسبق، تبدأ المكونات الديناميكية المغلفة بـ Suspense في البث من الخادم بالتوازي.

مخطط يظهر التزامن لشرائح المسار أثناء البث، يظهر جلب البيانات، التصيير، والتهيئة للأجزاء الفردية.

لتقليل حمل الشبكة، يتم إرسال الاستجابة الكاملة - بما في ذلك HTML الثابت والأجزاء الديناميكية المبثوثة - في طلب HTTP واحد. هذا يتجنب جولات إضافية ويحسن كل من التحميل الأولي والأداء العام.

تمكين التصيير الجزئي المسبق

يمكنك تمكين PPR عن طريق إضافة خيار ppr إلى ملف next.config.ts:

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}

export default nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}

تتيح لك القيمة 'incremental' اعتماد PPR لمسارات محددة:

/app/dashboard/layout.tsx
export const experimental_ppr = true

export default function Layout({ children }: { children: React.ReactNode }) {
  // ...
}
/app/dashboard/layout.js
export const experimental_ppr = true

export default function Layout({ children }) {
  // ...
}

المسارات التي لا تحتوي على experimental_ppr ستكون القيمة الافتراضية false ولن يتم تصييرها مسبقًا باستخدام PPR. تحتاج إلى اختيار PPR صراحة لكل مسار.

جيد أن تعرف:

  • سيتم تطبيق experimental_ppr على جميع الأبناء لقطاع المسار، بما في ذلك التخطيطات والصفحات المتداخلة. لا تحتاج إلى إضافتها إلى كل ملف، فقط القطاع العلوي للمسار.
  • لتعطيل PPR لقطاعات الأبناء، يمكنك تعيين experimental_ppr إلى false في قطاع الابن.

أمثلة

واجهات برمجية ديناميكية

عند استخدام واجهات برمجية ديناميكية تتطلب النظر إلى الطلب الوارد، سيقوم Next.js بالتحول إلى التصيير الديناميكي للمسار. لمواصلة استخدام PPR، قم بتغليف المكون بـ Suspense. على سبيل المثال، مكون <User /> ديناميكي لأنه يستخدم واجهة cookies البرمجية:

import { cookies } from 'next/headers'

export async function User() {
  const session = (await cookies()).get('session')?.value
  return '...'
}
import { cookies } from 'next/headers'

export async function User() {
  const session = (await cookies()).get('session')?.value
  return '...'
}

سيتم بث مكون <User /> بينما أي محتوى آخر داخل <Page /> سيتم تصييره مسبقًا ويصبح جزءًا من الهيكل الثابت.

import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'

export const experimental_ppr = true

export default function Page() {
  return (
    <section>
      <h1>سيتم تصيير هذا مسبقًا</h1>
      <Suspense fallback={<AvatarSkeleton />}>
        <User />
      </Suspense>
    </section>
  )
}
import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'

export const experimental_ppr = true

export default function Page() {
  return (
    <section>
      <h1>سيتم تصيير هذا مسبقًا</h1>
      <Suspense fallback={<AvatarSkeleton />}>
        <User />
      </Suspense>
    </section>
  )
}

تمرير خصائص ديناميكية

تصبح المكونات ديناميكية فقط عند الوصول إلى القيمة. على سبيل المثال، إذا كنت تقرأ searchParams من مكون <Page />، يمكنك تمرير هذه القيمة إلى مكون آخر كخاصية:

import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'

export default function Page({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  return (
    <section>
      <h1>سيتم تصيير هذا مسبقًا</h1>
      <Suspense fallback={<TableSkeleton />}>
        <Table searchParams={searchParams} />
      </Suspense>
    </section>
  )
}
import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'

export default function Page({ searchParams }) {
  return (
    <section>
      <h1>سيتم تصيير هذا مسبقًا</h1>
      <Suspense fallback={<TableSkeleton />}>
        <Table searchParams={searchParams} />
      </Suspense>
    </section>
  )
}

داخل مكون الجدول، الوصول إلى القيمة من searchParams سيجعل المكون ديناميكيًا بينما باقي الصفحة سيتم تصييرها مسبقًا.

export async function Table({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  const sort = (await searchParams).sort === 'true'
  return '...'
}
export async function Table({ searchParams }) {
  const sort = (await searchParams).sort === 'true'
  return '...'
}