التعامل مع ملف التهيئة next.config.js في Next.js

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

بإمكانك إنشاء الملف next.config.js أو next.config.mjs في جذر مشروعك وإلى جوار الملف package.json، إن كنت ترغب في تطبيق إعدادت متقدمة على تطبيق Next.js.

يُعد الملف next.config.jsوحدة Node.js وليس ملف JSON، ويُستخدم من قبل خادم Next.js وخلال مراحل بناء التطبيق ولا يُضمَّن في تجميعة المتصفح.

إليك مثالًا عن ملف التهيئة next.config.js:

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  /* ضع الإعدادات هنا  */
}

module.exports = nextConfig

إن احتجت إلى وحدات ECMAScript بإمكانك استخدام الملف:

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  /* ضع الإعدادات هنا  */
}

export default nextConfig

بإمكانك استخدام دالة أيضًا:

module.exports = (phase, { defaultConfig }) => {
  /**
   * @type {import('next').NextConfig}
   */
  const nextConfig = {
    /* ضع الإعدادات هنا  */
  }
  return nextConfig
}

يمكن ابتداءً بالنسخة 12.1.0 استخدام دالة متزامنة:

module.exports = async (phase, { defaultConfig }) => {
  /**
   * @type {import('next').NextConfig}
   */
  const nextConfig = {
    /* ضع الإعدادات هنا */
  }
  return nextConfig
}

يُعد السياق phase هو السياق الحالي لتحميل الإعدادات وتوجد سياقات أخرى أيضًا. يمكن إدراج المراحل phases من الوحدة next/constants:

const { PHASE_DEVELOPMENT_SERVER } = require('next/constants')

module.exports = (phase, { defaultConfig }) => {
  if (phase === PHASE_DEVELOPMENT_SERVER) {
    return {
      /* ضع إعدادات مرحلة التطوير هنا */
    }
  }

  return {
    /* ضع إعدادات كل المراحل هنا ما عدا مرحلة التطوير */
  }
}

تُوضع الإعدادات المسموحة) في next.config.js مكان أسطر التعليقات في الشيفرات السابقة. مع ذلك، لا حاجة إلى أية إعدادات، وليس من الضرورة فهم عمل كل إعداد. كل ما عليك هو البحث عن الميزات التي تريد تفعيلها أو تعديلها في هذه الصفحة وستريك ما العمل.

تفادى استخدام ميزات JavaScript الجديدة في اصدار Node.js الذي تستهدفه. فلن يُحلل الملف next.config.js من قبل Webpack أو Babel أو TypeScript.

متغيرات البيئة

تقدم ابتداءً من الإصدار 9.4 تجربة تعليمية وعملية في إضافة متغيرات البيئة. حاول أن تجربها.

لإضافة متغيرات بيئة إلى تجميعة JavaScript، افتح الملف next.config.js وأضف الإعداد env:

module.exports = {
  env: {
    customKey: 'my-value',
  },
}

بإمكانك الآن الوصول إلى process.env.customKey في شيفرتك. إليك مثالًا:

function Page() {
  return <h1>The value of customKey is: {process.env.customKey}</h1>
}

export default Page

تستبدل Next.js أثناء البناء المفاتيح process.env.customKey بالقيمة 'my-value'. وانتبه إلى أنك لن تستطيع تفكيك متغيرات process.env نظرًا لطبيعة الإضافة DefinePlugin في webpack. فلو ألقينا نظرة مثلًا على السطر البرمجي التالي:

return <h1>The value of customKey is: {process.env.customKey}</h1>

سينتهي الأمر على النحو:

return <h1>The value of customKey is: {'my-value'}</h1>

تحذير: متغيرات البيئة المُعرَّفة بهذه الطريقة ستُضاف إلى تجميعة JavaScript المُرسلة إلى العميل خلاف إضافتها في ملف env. والسلوك الذي تسلكه إلى أضفنا العبارة NEXT_PUBLIC_‎ أو لا في بداية اسم متغير البيئة.

المسار الأساسي basePath

أضيف بدءا من الإصدار 9.5.0.

بإمكانك استخدام الإعداد basePath لنشر تطبيق Next.js ضمن مسار فرعي لنطاق، إذ يسمح لك هذا الإعداد بضبط بادئة للمسار في تطبيقك. ولكي تستخدم مثلًا المسار docs/ بدلًا من المسار الأساسي الافتراضي /، افتح الملف next.config.js وأضف الإعداد basePath كالتالي:

module.exports = {
  basePath: '/docs',
}

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

الروابط

عند ربط الصفحة بغيرها باستخدام next/link و next/router سيُطبق الإعداد basePath تلقائيًا. إذ سيتحول على سبيل المثال المسار about/ إلى docs/about/ تلقائيًا عند ضبط قيمة الإعداد basePath على docs/.

export default function HomePage() {
  return (
    <>
      <Link href="/about">
        <a>About Page</a>
      </Link>
    </>
  )
}

وسيكون خرج HTML كالتالي:

<a href="/docs/about">About Page</a>

يضمن ذلك أنك لن تُضطر إلى تغيير كل الروابط في تطبيقك عند تغيير قيمة الإعداد basePath.

الصور

لا بد من إضافة قيمة الإعداد basePath قبل قيمة الخاصية src إن كنت تريد استخدام المكوّن next/image. فالمسار docs/me.png/ سيخدّم صورتك بالشكل الصحيح إن قررت أن تكون قيمة الإعداد basePath هي docs/.

import Image from 'next/image'

function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src="/docs/me.png"
        alt="Picture of the author"
        width={500}
        height={500}
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

export default Home

إعادة الكتابة Rewrites

سجل التغييرات
الإصدار التغييرات
13.3.1 إضافة missing
10.2.0 إضافة has
9.5.0 إضافة خاصية إعادة الكتابة

تتيح لك عملية إعادة الكتابة Rewrites ربط مسار طلب وارد إلى مسار وجهة أخرى. تعمل إعادة الكتابة مثل وسيط لعناوين URL وتقنِّع المسار إلى الوجهة ليظهر المستخدم وكأنه لم يغير مكانه في الموقع. بالمقابل تعيد عملية إعادة التوجيه redirects توجيه المستخدم إلى صفحة جديدة وتُظهر التغييرات على العنوان.

ولتستخدم إعادة الكتابة أضف المفتاح rewrites إلى ملف التهيئة next.config.js:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/about',
        destination: '/',
      },
    ]
  },
}

تُطبق إعادة الكتابة على التوجيه في جانب العميل، وتطبق إعادة الكتابة في المثال السابق مثلًا على العنصر <"Link href="/about>.

إن الدالة هي دالة غير متزامنة تتوقع إعادة مصفوفة تضم كائنات تمتلك الخاصيتين source و destination:

  • source: من النوع String، وهو نموذج مسار الطلب الوارد.
  • destination: من النوع String، وهو المسار الذي تريد التوجه إليه.
  • basePath: تأخذ أحد القيمتين false أو undefined، فإن كانت القيمة false، لن يضاف المسار الأساسي إلى العنوان عند المطابقة، ويستخدم في إعادة الكتابة الخارجية.
  • locale: تأخذ أحد القيمتين false أو undefined، يحدد إن كان سُضاف الإعداد المحلي إلى المسار عند المطابقة.
  • has: مصفوفة من الكائنات has objects لها الخاصيات type و key و value.
  • missing: مصفوفة من كائنات لها الخاصيات type و key و value.

تجري عملية إعادة الكتابة بعد التحقق من منظومة الملفات (ملفات المجلدين pages و public) وقبل التوجه الديناميكي افتراضيًا. يمكن تغيير هذا السلوك بإعادة كائن بدلًا من المصفوفة من الدالة rewrites ابتداءً من الإصدار 10.1 من Next.js:

module.exports = {
  async rewrites() {
    return {
      beforeFiles: [
        //يجري التحقق من عمليات إعادة الكتابة هذه بعد الترويسات 
        // أو إعادة التوجيه
        //_next/public وقبل كل الملفات بما فيها ملفات المجلد 
        // مما يسمح بتغيير ملفات الصفحة
        {
          source: '/some-page',
          destination: '/somewhere-else',
          has: [{ type: 'query', key: 'overrideMe' }],
        },
      ],
      afterFiles: [
        // pages/public يجري التحقق من عمليات إعادة الكتابة هذه بعد ملفات
        // لكن قبل الوجهات الديناميكية 
       
        {
          source: '/non-existent',
          destination: '/somewhere-else',
        },
      ],
      fallback: [
        // pages/public يجري التحقق من عمليات إعادة الكتابة هذه بعد ملفات
        // وقبل الوجهات الديناميكية 
       
        {
          source: '/:path*',
          destination: `https://my-old-site.com/:path*`,
        },
      ],
    }
  },
}

ملاحظة: إنّ إجراء إعادة كتابة ضمن beforeFiles لا يتضمن التحقق المباشر من نظام الملفات والوجهات الديناميكية بعد مطابقة المصدر، بل تستمر حتى يجري التحقق من جميع الإجراءات ضمن beforeFiles. إليك ترتيب التحقق من وجهات Next.js:

  1. يجري التحقق من الترويسات headers أو تطبيقها.
  2. يجري التحقق من عمليات إعادة التوجيه redirects أو تطبيقها.
  3. يجري التحقق من عمليات إعادة الكتابة ضمن beforeFiles أو تطبيقها.
  4. يجري التحقق من الملفات الساكنة في المجلد public و next/static_ والصفحات غير الديناميكية أو تخديمها.
  5. يجري التحقق من عمليات إعادة الكتابة ضمن afterFiles أو تطبيقها، فإن تطابقت إحدى عمليات إعادة الكتابة، نتحقق من الوجهة الديناميكية أو الملفات الساكنة بعد كل تطابق.
  6. يجري التحقق من إعادة الكتابة ضمن fallback أو تطبيقها، ويجري تطبيقها قبل تصيير الصفحة 404، وبعد التحقق من الوجهات الديناميكية وجميع الموجودات الساكنة. إن كنت تستخدم fallback: true/'blocking'‎ في getStaticPaths فلن تعمل عملية إعادة الكتابة ضمن fallback.

معاملات الدالة rewrites

عند استخدام المعاملات، تُمرر هذه المعاملات إلى الدالة عبر الاستعلام افتراضيًا إن لم يُستخدم أيًا منها في الخاصية destination:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/old-about/:path*',
        destination: '/about', //وبالتالي سيُمرر path لم يُستخدم المعامل 
          // تلقائيًا ضمن الاستعلام
      },
    ]
  },
}

أما إن استُخدم معامل في الوجهة destination، فلن يُمرر أي من المعاملات تلقائيًا ضمن الاستعلام:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/docs/:path*',
        destination: '/:path*',//وبالتالي لن يُمرر path استُخدم المعامل
      // أي معامل تلقائيًا من خلال الاستعلام
      },
    ]
  },
}

لكن لا يزال بإمكانك تمرير المعاملات يدويًا ضمن الاستعلام إن استُخدم أحدها في الخاصية destination:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/:first/:second',
        destination: '/:first?second=:second',
          
        //قد استُخدم في الوجهة فلن يُضاف  first طالما أن المعامل 
        // تلقائيًا  second المعامل الثاني 
        // إلى الاستعلام على الرغم من إمكانية إضافته يدويًا كما في الأعلى
          
      },
    ]
  },
}

ملاحظة: تُحلَّل معاملات إعادة الكتابة للصفحات الساكنة الناتجة عن التحسين التلقائي الساكن أو التصيير المسبق من جانب العميل بعد الترطيب ويُزوَّد به الاستعلام.

مطابقة المسارات

يُسمح بمطابقة المسارات كأن يُطابق المسار blog/:slug/ المسار blog/hello-world/ (دون تداخل في المسارات).

module.exports = {
  async rewrites() {
    return [
      {
        source: '/blog/:slug',
        destination: '/news/:slug', // يمكن مطابقة المعاملات هنا
      },
    ]
  },
}

مطابقة المسارات بوجود محارف بديلة

لمطابقة مسار باستخدام محارف بديلة، بإمكانك استعمال * بعد المعامل، إذ سيُطابق المسار *blog/:slug/ مثلًا المسار /blog/a/b/c/d/hello-world:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/blog/:slug*',
        destination: '/news/:slug*', // يمكن مطابقة المعاملات هنا
      },
    ]
  },
}

مطابقة المسارات من خلال التعابير النمطية Regex

لمطابقة مسار من خلال التعابير النمطية، غلِّف التعبير النمطي ضمن قوسين بعد المعامل. سيطابق المسار blog/:slug(\\d{1,})/ مثلًا المسار blog/123/ وليس المسار blog/abc/:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/old-blog/:post(\\d{1,})',
        destination: '/blog/:post', // يمكن مطابقة المعاملات هنا
      },
    ]
  },
}

تُستخدم المحارف التالية ( و ) و { و } و : و * و + و ? في صياغة التعابير النمطية المستخدمة لمطابقة المسارات، فإن استُخدمت في الخاصية source كقيم غير خاصة non-special لا بد من تجاوزها بإضافة الصيغة \\ قبلها:

module.exports = {
  async rewrites() {
    return [
      {
        //`/english(default)/something` سيثطابق ذلك  
        source: '/english\\(default\\)/:slug',
        destination: '/en-us/:slug',
      },
    ]
  },
}

مطابقة الترويسات وملفات تعريف الارتباط والاستعلام

بالإمكان مطابقة عملية إعادة كتابة عندما تُطابِق قيم الترويسات أو ملف تعريف الارتباط أو الاستعلام قيمة الحقل has أو لا تطابق قيمة الحقل missing. أي يجب أن تتطابق الخاصية source وجميع عناصر has حتى تُطبَّق إعادة الكتابة ولا يجب أن تتطابق جميع عناصر missing. تمتلك العناصر has و missing الحقول التالية:

  • type: من النوع String، وسيكون إما header أو cookie أو host أو query.
  • key: من النوع String، مفتاح النوع المختار type الذي سيُطابق.
  • value: من النوع String أو undefined، القيمة التي سيجري التحقق منها، وإن كانت غير محددة ستُطابق أي قيمة. يمكن استخدام سلسلة نصية تشابه التعبير النمطي لالتقاط جزء محدد من القيمة. فإن استُخدمت القيمة first-(?<paramName>.*) لمطابقة first-second، ستتمكن حينها من استخدام second في الوجهة مع المعامل ‎:paramName.
module.exports = {
  async rewrites() {
    return [
      // `x-rewrite-me` إن وجدت الترويسة 
      // تُطبق إعادة الكتابة
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-rewrite-me',
          },
        ],
        destination: '/another-page',
      },
      // `x-rewrite-me` إن فقدت الترويسة 
      // تُطبق إعادة الكتابة
            {
        source: '/:path*',
        missing: [
          {
            type: 'header',
            key: 'x-rewrite-me',
          },
        ],
        destination: '/another-page',
      },
      // إن تطابقت الترويسة أو ملف الارتباط أو الاستعلام 
      // تُطبق إعادة الكتابة
      
      {
        source: '/specific/:path*',
        has: [
          {
            type: 'query',
            key: 'page',
            //لن تكون قيمة الصفحة متاحة في الوجهة لأن القيمة موجودة
            //(?<page>home) ولا تُستخدم مجموعة التقاط مُسماة مثل 
           
            value: 'home',
          },
          {
            type: 'cookie',
            key: 'authorized',
            value: 'true',
          },
        ],
        destination: '/:path*/home',
      },
      //وتحتوي على قيمة التطابق `x-authorized` إن وجدت الترويسة 
      //ستُطيّق إعادة الكتابة
    
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-authorized',
            value: '(?<authorized>yes|true)',
          },
        ],
        destination: '/home?authorized=:authorized',
      },
      //ستُطبّق إعادة الكتابة `example.com` إن كان المضيف 
    
      {
        source: '/:path*',
        has: [
          {
            type: 'host',
            value: 'example.com',
          },
        ],
        destination: '/another-page',
      },
    ]
  },
}

إعادة الكتابة إلى عنوان URL خارجي

بإمكانك إعادة الكتابة إلى عنوان URL خارجي، وهذا مفيد لتبني Next.js تدريجيًا. إليك مثالًا عن إعادة كتابة لتحويل الوجهة blog/ لتطبيقك الرئيسي إلى موقع خارجي:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/blog',
        destination: 'https://example.com/blog',
      },
      {
        source: '/blog/:slug',
        destination: 'https://example.com/blog/:slug',
      },
    ]
  },
}

إن كنت تستخدم الحقل trailingSlash: true فستحتاج أيضًا إلى حشر محرف / في نهاية قيمة المعامل source. وإن كان الخادم الهدف يتوقع وجود المحرف / في نهاية القيمة، فلا بد من وضعها في المعامل destination أيضًا.

module.exports = {
  trailingSlash: true,
  async rewrites() {
    return [
      {
        source: '/blog/',
        destination: 'https://example.com/blog/',
      },
      {
        source: '/blog/:path*/',
        destination: 'https://example.com/blog/:path*/',
      },
    ]
  },
}

راجع هذين المثالين:

تبني Next.js تدريجيًا

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

module.exports = {
  async rewrites() {
    return {
      fallback: [
        {
          source: '/:path*',
          destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`,
        },
      ],
    }
  },
}

لمزيد من المعلومات راجع الصفحة "الانتقال إلى Next.js" من هذا التوثيق.

إعادة الكتابة مع دعم للمسار الأساسي

عند استخدام basePath مع إعادة الكتابة ستُضاف تلقائيًا البادئة إلى المعاملين source و destination ما لم تضف الإعداد basePath: false إلى دالة إعادة الكتابة:

module.exports = {
  basePath: '/docs',

  async rewrites() {
    return [
      {
        source: '/with-basePath', // /docs/with-basePath يصبج تلقائيًا 
        destination: '/another', // /docs/another يصبح تلقائيًا 
      },
      {
        //basePath: false لأن /without-basePath إلى /docs لا يضيف   
        // لا يمكن استخدام ذلك في عمليات إعادة الكتابة الداخلية
        // destination: '/another' مثال 
        source: '/without-basePath',
        destination: 'https://example.com',
        basePath: false,
      },
    ]
  },
}

إعادة الكتابة مع دعم التوجه i18n

عند استخدام i18n مع إعادة الكتابة ستُضاف تلقائيًا البادئة المهيأة مسبقًا locales إلى المعاملين source و destination ما لم تضف الإعداد locale: false إلى دالة إعادة الكتابة، وفي حال استخدامه لا بد من إضافة تلك البادئة يدويًا حتى تُطابق بشكل صحيح:

module.exports = {
  i18n: {
    locales: ['en', 'fr', 'de'],
    defaultLocale: 'en',
  },

  async rewrites() {
    return [
      {
        source: '/with-locale', // يتعامل مع الإعداد المحلي تلقائيًا
        destination: '/another', // يمرر الإعداد المحلي تلقائيًا
      },
      {
        // لا يُعالج الإعداد المحلي تلقائيًا
        source: '/nl/with-locale-manual',
        destination: '/nl/another',
        locale: false,
      },
      {
        // '/' يطابق 
        // `en` لأن الإعداد المحلي الافتراضي 
        source: '/en',
        destination: '/en/another',
        locale: false,
      },
      {
        // locale: false بإمكانك مطابقة جميع الإعدادات المحلية عندما يكون
        source: '/:locale/api-alias/:path*',
        destination: '/api/:path*',
        locale: false,
      },
      {
        // /(en|fr|de)/(.*)  سيحول هذا إلى  
        // لذا لن يُطابق المستوى الأعلى
        // `/` أو `/fr` 
        source: '/(.*)',
        destination: '/another',
      },
    ]
  },
}

إعادة التوجيه

سجل التغييرات
الإصدار التغييرات
10.2.0 إضافة has
9.5.0 إضافة خاصية إعادة التوجيه

يسمح لك إعادة التوجيه أن تحوّل مسار الطلب الوارد إلى وجهة مختلفة. ولاستخدام إعادة التوجيه، لا بد من إضافة المفتاح redirects إلى ملف التهيئة next.config.js:

module.exports = {
  async redirects() {
    return [
      {
        source: '/about',
        destination: '/',
        permanent: true,
      },
    ]
  },
}

تُعد الدالة redirects دالة غير متزامنة تتوقع إعادة مصفوفة تضم كائنات تمتلك الخاصيات source و destination و permanent:

  • source: من النوع String، وهو نموذج مسار الطلب الوارد.
  • destination: من النوع String، وهو المسار الذي تريد التوجه إليه.
  • permanent: من النوع المنطقي، فإن كانت قيمتها true سيُستخدم رمز الحالة 308 الذي يملي على العميل أو محرك البحث تخزين إعادة التوجيه إلى ما لا نهاية. أما في حال كان false سيُستخدم رمز الحالة 307 وهو مؤقت ولا حاجة لتخزينه.

    قد تلاحظ استخدام رمز الحالة 307 لإعادة التوجه المؤقت و 308 لإعادة التوجه الدائم مع ()redirect. وعلى الرغم من استخدام الرمزين 302 و 301 على الترتيب تقليديًا، إلا أن العديد من المتصفحات قد غيرت نوع طلب إعادة التوجه إلى GET بغض النظر عن أصول نوع الطلب. فلو أنشأ المتصفح طلبًا إلى POST /v1/users وأعاد رمز الحالة 302 مع الموقع v2/users/، فقد تكون الطلبات التالية من النوع GET /v2/users بدلًا من النوع المتوقع POST /v2/users. وبالتالي استخدمت Next.js رمزي الحالة 308 و 307 لحفظ نوع الطلب صراحة.

  • basePath: تأخذ أحد القيمتين false أو undefined، فإن كانت القيمة false، لن يضاف المسار الأساسي إلى العنوان عند المطابقة، ويستخد في إعادة الكتابة الخارجية.
  • locale: تأخذ أحد القيمتين false أو undefined، يحدد إن كان سُضاف الإعداد المحلي إلى المسار عند المطابقة.
  • has: مصفوفة من الكائنات has objects لها الخاصيات type و key و value.
  • missing: مصفوفة من كائنات لها الخاصيات type و key و value.

يجري التحقق من عمليات إعادة التوجه قبل التحقق من نظام الملفات الذي يضم ملفات المجلدين pages و public. ولا تُطبَّق عمليات إعادة التوجيه على التوجيه من طرف العميل (Link أو router.push) إلا إن تدخلت البرمجيات الوسيطة Middleware وطابقت المسار.

عندما يُطبق إعادة التوجيه، ستُمرر أي قيم للاستعلام ضمن الطلب إلى الوجهة الجديدة. ألق نظرة على الإعدادات التالية:

{
  source: '/old-blog/:path*',
  destination: '/blog/:path*',
  permanent: false
}

فعندما يكون الطلب من الشكل old-blog/post-1?hello=world/ مثلًا سيُعاد توجيه العميل إلى blog/post-1?hello=world/.

مطابقة المسارات

يُسمح بمطابقة المسارات كأن يُطابق المسار blog/:slug/ المسار blog/hello-world/ (دون تداخل في المسارات).

module.exports = {
  async redirects() {
    return [
      {
        source: '/old-blog/:slug',
        destination: '/news/:slug', // Matched parameters can be used in the destination
        permanent: true,
      },
    ]
  },
}

مطابقة المسارات بوجود محارف بديلة

لمطابقة مسار باستخدام محارف بديلة، بإمكانك استعمال * بعد المعامل، إذ سيُطابق المسار *blog/:slug/ مثلًا المسار blog/a/b/c/d/hello-world/:

module.exports = {
  async redirects() {
    return [
      {
        source: '/blog/:slug*',
        destination: '/news/:slug*', // Matched parameters can be used in the destination
        permanent: true,
      },
    ]
  },
}

مطابقة المسارات من خلال التعابير النمطية Regex

لمطابقة مسار من خلال التعابير النمطية، غلِّف التعبير النمطي ضمن قوسين بعد المعامل. سيطابق المسار post/:slug(\\d{1,})/ مثلًا المسار post/123/ وليس المسار post/abc/:

module.exports = {
  async redirects() {
    return [
      {
        source: '/post/:slug(\\d{1,})',
        destination: '/news/:slug', // Matched parameters can be used in the destination
        permanent: false,
      },
    ]
  },
}

تُستخدم المحارف التالية ( و ) و { و } و : و * و + و ? في صياغة التعابير النمطية المستخدمة لمطابقة المسارات، فإن استُخدمت في الخاصية source كقيم غير خاصة non-special لا بد من تجاوزها بإضافة الصيغة \\ قبلها:

module.exports = {
  async redirects() {
    return [
      {
        // this will match `/english(default)/something` being requested
        source: '/english\\(default\\)/:slug',
        destination: '/en-us/:slug',
        permanent: false,
      },
    ]
  },
}

مطابقة الترويسات وملفات تعريف الارتباط والاستعلام

بالإمكان مطابقة عملية إعادة كتابة عندما تُطابِق قيم الترويسات أو ملف تعريف الارتباط أو الاستعلام قيمة الحقل has فقط. أي يجب أن تتطابق الخاصية source وجميع عناصر has حتى تُطبَّق إعادة الكتابة. تمتلك العناصر has و missing الحقول التالية:

  • type: من النوع String، وسيكون إما header أو cookie أو host أو query.
  • key: من النوع String، مفتاح النوع المختار type الذي سيُطابق.
  • value: من النوع String أو undefined، القيمة التي سيجري التحقق منها، وإن كانت غير محددة ستُطابق أي قيمة. يمكن استخدام سلسلة نصية تشابه التعبير النمطي لالتقاط جزء محدد من القيمة. فإن استُخدمت القيمة first-(?<paramName>.*) لمطابقة first-second، ستتمكن حينها من استخدام second في الوجهة مع المعامل :paramName.
module.exports = {
  async redirects() {
    return [
      // if the header `x-redirect-me` is present,
      // this redirect will be applied
      {
        source: '/:path((?!another-page$).*)',
        has: [
          {
            type: 'header',
            key: 'x-redirect-me',
          },
        ],
        permanent: false,
        destination: '/another-page',
      },
      // if the header `x-dont-redirect` is present,
      // this redirect will be applied
      {
        source: '/:path((?!another-page$).*)',
        missing: [
          {
            type: 'header',
            key: 'x-do-not-redirect',
          },
        ],
        permanent: false,
        destination: '/another-page',
      },
      // if the source, query, and cookie are matched,
      // this redirect will be applied
      {
        source: '/specific/:path*',
        has: [
          {
            type: 'query',
            key: 'page',
            // the page value will not be available in the
            // destination since value is provided and doesn't
            // use a named capture group e.g. (?<page>home)
            value: 'home',
          },
          {
            type: 'cookie',
            key: 'authorized',
            value: 'true',
          },
        ],
        permanent: false,
        destination: '/another/:path*',
      },
      // if the header `x-authorized` is present and
      // contains a matching value, this redirect will be applied
      {
        source: '/',
        has: [
          {
            type: 'header',
            key: 'x-authorized',
            value: '(?<authorized>yes|true)',
          },
        ],
        permanent: false,
        destination: '/home?authorized=:authorized',
      },
      // if the host is `example.com`,
      // this redirect will be applied
      {
        source: '/:path((?!another-page$).*)',
        has: [
          {
            type: 'host',
            value: 'example.com',
          },
        ],
        permanent: false,
        destination: '/another-page',
      },
    ]
  },
}

إعادة التوجيه مع دعم للمسار الأساسي basePath

عند استخدام basePath مع إعادة التوجيه ستُضاف تلقائيًا البادئة إلى المعاملين source و destination ما لم تضف الإعداد basePath: false إلى دالة إعادة التوجيه:

module.exports = {
  basePath: '/docs',

  async redirects() {
    return [
      {
        source: '/with-basePath', // automatically becomes /docs/with-basePath
        destination: '/another', // automatically becomes /docs/another
        permanent: false,
      },
      {
        // does not add /docs since basePath: false is set
        source: '/without-basePath',
        destination: '/another',
        basePath: false,
        permanent: false,
      },
    ]
  },
}

إعادة التوجيه مع دعم التوجه i18n

عند استخدام i18n مع إعادة الكتابة ستُضاف تلقائيًا البادئة المهيأة مسبقًا locales إلى المعاملين source و destination ما لم تضف الإعداد locale: false إلى دالة إعادة التوجيه، وفي حال استخدامه لا بد من إضافة تلك البادئة يدويًا حتى تُطابق بشكل صحيح:

module.exports = {
  i18n: {
    locales: ['en', 'fr', 'de'],
    defaultLocale: 'en',
  },

  async redirects() {
    return [
      {
        source: '/with-locale', // automatically handles all locales
        destination: '/another', // automatically passes the locale on
        permanent: false,
      },
      {
        // does not handle locales automatically since locale: false is set
        source: '/nl/with-locale-manual',
        destination: '/nl/another',
        locale: false,
        permanent: false,
      },
      {
        // this matches '/' since `en` is the defaultLocale
        source: '/en',
        destination: '/en/another',
        locale: false,
        permanent: false,
      },
      // it's possible to match all locales even when locale: false is set
      {
        source: '/:locale/page',
        destination: '/en/newpage',
        permanent: false,
        locale: false,
      }
      {
        // this gets converted to /(en|fr|de)/(.*) so will not match the top-level
        // `/` or `/fr` routes like /:path* would
        source: '/(.*)',
        destination: '/another',
        permanent: false,
      },
    ]
  },
}

قد تحتاج في حالات نادرة تعيين رموز حالة مخصصة لعملاء HTTP الأقدم كي يُعاد توجيههم بالشكل الصحيح. في حالات كهذه، استخدم الخاصية statusCode بدلًا من permanent لكن لا تستخدمهما معًا. تجدر الإشارة إلى إضافة الترويسة Refresh تلقائيًا إلى رمز الحالة 308 لضمان التوافق مع المتصفح IE11.

أنواع أخرى لإعادة التوجيه

  • بإمكانك استخدام ()res.redirect ضمن وجهات API.
  • يمكنك إعاة توجيه صفحات محددة عند الطلب ضمن getStaticProps و getServerSideProps.

انظر مثال Redirects.

الترويسات

سجل التغييرات
الإصدار التغييرات
10.2.0 إضافة has
9.5.0 إضافة خاصية الترويسات

تتيح لك إعداد ترويسات HTTP مخصصة عند الاستجابة إلى طلب وارد عبر مسار معين. ولإعداد ترويسة مخصصة، استخدم المفتاح headers ضمن الملف next.config.js:

module.exports = {
  async headers() {
    return [
      {
        source: '/about',
        headers: [
          {
            key: 'x-custom-header',
            value: 'my custom header value',
          },
          {
            key: 'x-another-custom-header',
            value: 'my other custom header value',
          },
        ],
      },
    ]
  },
}

تُعد الدالة headers دالة غير متزامنة تتوقع إعادة مصفوفة تضم كائنات تمتلك الخاصيات source و headers:

  • source: من النوع String، وهو نموذج مسار الطلب الوارد.
  • headers: مصفوفة من كائنات ترويسة الاستجابة، ولها الخاصيتان key و value.
  • basePath: تأخذ أحد القيمتين false أو undefined، فإن كانت القيمة false، لن يضاف المسار الأساسي إلى العنوان عند المطابقة، ويستخدم في إعادة الكتابة الخارجية.
  • locale: تأخذ أحد القيمتين false أو undefined، يحدد إن كان سُضاف الإعداد المحلي إلى المسار عند المطابقة.
  • has: مصفوفة من الكائنات has objects لها الخاصيات type و key و value.
  • missing: مصفوفة من كائنات لها الخاصيات type و key و value.

يجري التحقق من الترويسات قبل التحقق من منظومة الملفات التي تضم ملفات المجلدين pages و public.

سلوك الإلغاء في الترويسات

إن تطابقت ترويستان مع المسار ذاته واستهدفتا نفس المفتاح، سيُلغي مفتاح الترويسة الأخيرة مفتاح الأولى ويحل محله. في المثال التالي، سينتج المسار hello/ عن الترويسة x-hello التي قيمتها world لأنها الترويسة الأخيرة من ترويستين لهما نفس المفتاح x-hello:

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'x-hello',
            value: 'there',
          },
        ],
      },
      {
        source: '/hello',
        headers: [
          {
            key: 'x-hello',
            value: 'world',
          },
        ],
      },
    ]
  },
}

مطابقة المسارات

يُسمح بمطابقة المسارات كأن يُطابق المسار blog/:slug/ المسار blog/hello-world/ (دون تداخل في المسارات).

module.exports = {
  async headers() {
    return [
      {
        source: '/blog/:slug',
        headers: [
          {
            key: 'x-slug',
            value: ':slug', // Matched parameters can be used in the value
          },
          {
            key: 'x-slug-:slug', // Matched parameters can be used in the key
            value: 'my other custom header value',
          },
        ],
      },
    ]
  },
}

مطابقة المسارات بوجود محارف بديلة

لمطابقة مسار باستخدام محارف بديلة، بإمكانك استعمال * بعد المعامل، إذ سيُطابق المسار *blog/:slug/ مثلًا المسار blog/a/b/c/d/hello-world/:

module.exports = {
  async headers() {
    return [
      {
        source: '/blog/:slug*',
        headers: [
          {
            key: 'x-slug',
            value: ':slug*', // Matched parameters can be used in the value
          },
          {
            key: 'x-slug-:slug*', // Matched parameters can be used in the key
            value: 'my other custom header value',
          },
        ],
      },
    ]
  },
}

مطابقة المسارات من خلال التعابير النمطية Regex

لمطابقة مسار من خلال التعابير النمطية، غلِّف التعبير النمطي ضمن قوسين بعد المعامل. سيطابق المسار blog/:slug(\\d{1,})/ مثلًا المسار blog/123/ وليس المسار blog/abc/:

module.exports = {
  async headers() {
    return [
      {
        source: '/blog/:post(\\d{1,})',
        headers: [
          {
            key: 'x-post',
            value: ':post',
          },
        ],
      },
    ]
  },
}

تُستخدم المحارف التالية ( و ) و { و } و : و * و + و ? في صياغة التعابير النمطية المستخدمة لمطابقة المسارات، فإن استُخدمت في الخاصية source كقيم غير خاصة non-special لا بد من تجاوزها بإضافة الصيغة \\ قبلها:

module.exports = {
  async headers() {
    return [
      {
        // this will match `/english(default)/something` being requested
        source: '/english\\(default\\)/:slug',
        headers: [
          {
            key: 'x-header',
            value: 'value',
          },
        ],
      },
    ]
  },
}

مطابقة الترويسات وملفات تعريف الارتباط والاستعلام

بالإمكان مطابقة ترويسة عندما تُطابِق قيم ترويسة أو ملف تعريف الارتباط أو الاستعلام قيمة الحقل has فقط. أي يجب أن تتطابق الخاصية source وجميع عناصر has حتى تُطبَّق إعادة الكتابة. تمتلك العناصر has و missing الحقول التالية:

  • type: من النوع String، وسيكون إما header أو cookie أو host أو query.
  • key: من النوع String، مفتاح النوع المختار type الذي سيُطابق.
  • value: من النوع String أو undefined، القيمة التي سيجري التحقق منها، وإن كانت غير محددة ستُطابق أي قيمة. يمكن استخدام سلسلة نصية تشابه التعبير النمطي لالتقاط جزء محدد من القيمة. فإن استُخدمت القيمة first-(?<paramName>.*) لمطابقة first-second، ستتمكن حينها من استخدام second في الوجهة مع المعامل :paramName.
module.exports = {
  async headers() {
    return [
      // if the header `x-add-header` is present,
      // the `x-another-header` header will be applied
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-add-header',
          },
        ],
        headers: [
          {
            key: 'x-another-header',
            value: 'hello',
          },
        ],
      },
      // if the header `x-no-header` is not present,
      // the `x-another-header` header will be applied
      {
        source: '/:path*',
        missing: [
          {
            type: 'header',
            key: 'x-no-header',
          },
        ],
        headers: [
          {
            key: 'x-another-header',
            value: 'hello',
          },
        ],
      },
      // if the source, query, and cookie are matched,
      // the `x-authorized` header will be applied
      {
        source: '/specific/:path*',
        has: [
          {
            type: 'query',
            key: 'page',
            // the page value will not be available in the
            // header key/values since value is provided and
            // doesn't use a named capture group e.g. (?<page>home)
            value: 'home',
          },
          {
            type: 'cookie',
            key: 'authorized',
            value: 'true',
          },
        ],
        headers: [
          {
            key: 'x-authorized',
            value: ':authorized',
          },
        ],
      },
      // if the header `x-authorized` is present and
      // contains a matching value, the `x-another-header` will be applied
      {
        source: '/:path*',
        has: [
          {
            type: 'header',
            key: 'x-authorized',
            value: '(?<authorized>yes|true)',
          },
        ],
        headers: [
          {
            key: 'x-another-header',
            value: ':authorized',
          },
        ],
      },
      // if the host is `example.com`,
      // this header will be applied
      {
        source: '/:path*',
        has: [
          {
            type: 'host',
            value: 'example.com',
          },
        ],
        headers: [
          {
            key: 'x-another-header',
            value: ':authorized',
          },
        ],
      },
    ]
  },
}

الترويسات مع دعم للمسار الأساسي basePath

عند استخدام basePath مع إعادة الترويسات ستُضاف تلقائيًا البادئة إلى المعامل source ما لم تضف الإعداد basePath: false إلى دالة الترويسة:

module.exports = {
  basePath: '/docs',

  async headers() {
    return [
      {
        source: '/with-basePath', // becomes /docs/with-basePath
        headers: [
          {
            key: 'x-hello',
            value: 'world',
          },
        ],
      },
      {
        source: '/without-basePath', // is not modified since basePath: false is set
        headers: [
          {
            key: 'x-hello',
            value: 'world',
          },
        ],
        basePath: false,
      },
    ]
  },
}

إعادة التوجيه مع دعم التوجه i18n

عند استخدام i18n مع الترويسات ستُضاف تلقائيًا البادئة المهيأة مسبقًا locales إلى المعامل source ما لم تضف الإعداد locale: false إلى الترويسة. وفي حال استخدامه لا بد من إضافة تلك البادئة يدويًا حتى تُطابق بشكل صحيح:

module.exports = {
  i18n: {
    locales: ['en', 'fr', 'de'],
    defaultLocale: 'en',
  },

  async headers() {
    return [
      {
        source: '/with-locale', // automatically handles all locales
        headers: [
          {
            key: 'x-hello',
            value: 'world',
          },
        ],
      },
      {
        // does not handle locales automatically since locale: false is set
        source: '/nl/with-locale-manual',
        locale: false,
        headers: [
          {
            key: 'x-hello',
            value: 'world',
          },
        ],
      },
      {
        // this matches '/' since `en` is the defaultLocale
        source: '/en',
        locale: false,
        headers: [
          {
            key: 'x-hello',
            value: 'world',
          },
        ],
      },
      {
        // this gets converted to /(en|fr|de)/(.*) so will not match the top-level
        // `/` or `/fr` routes like /:path* would
        source: '/(.*)',
        headers: [
          {
            key: 'x-hello',
            value: 'world',
          },
        ],
      },
    ]
  },
}

الترويسة Cache-Control

يمكن إعداد الترويسة Cache-Control ضمن وجهات API الخاصة بتطبيقك عن طريق التابع res.setHeader:

// pages/api/user.js

export default function handler(req, res) {
  res.setHeader('Cache-Control', 's-maxage=86400')
  res.status(200).json({ name: 'John Doe' })
}

لا يمكن إعداد ترويسات Cache-Control ضمن الملف next.config.js لأنها ستُلغى عند بناء نسخة الإنتاج، وذلك للتأكد من التخزين الفعّال لوجهات API والموجودات الساكنة static assets.

إن أردت إعادة التحقق من الذاكرة المؤقتة لصفحة وُلِّدت بشكل ساكن، عليك ضبط الخاصية revalidate ضمن الدالة getStaticProps في الصفحة.

الامتدادات المخصصة للصفحات

وجهت هذه الامتدادات إلى وحدات برمجية مثل التي تضيف دعمًا للصفحات التي تحمل الامتداد. يمكن أن تهيئ الامتدادات التي يجري البحث عنها ضمن المجلد pages عند تحليل الصفحة بفتح الملف next.config.js وإضافة الإعداد pageExtensions:

module.exports = {
  pageExtensions: ['mdx', 'md', 'jsx', 'js', 'tsx', 'ts'],
}

القيم الافتراضية للإعداد pageExtensions هي ['tsx', 'ts', 'jsx', 'js'].

يؤثر تهيئة الإعداد pageExtensions على كل الصفحات بما فيها:

  • middleware.js
  • pages/_document.js
  • pages/_app.js
  • /pages/api

إذ يعني مثلًا الإعداد ['page.tsx', 'page.ts']:pageExtensions، أن جميع الملفات مثل app.tsx_ ينبغي أن تُعاد تسميتها إلى app.page.tsx_ و middleware.page.ts و pages/users.page.tsx مثلًا.

تضمين الملفات من غير الصفحات في المجلد pages

لكي تشير إلى الموقع المشترك لملفات الاختبار والملفات المولَّدة وغيرها من الملفات التي تستخدمها المكوّنات في المجلد pages، بإمكانك وضع بادئة قبل الامتداد مثل page. افتح الملف next.config.js وأضف الإعداد pageExtensions:

module.exports = {
  pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'],
}

أعد بعد ذلك تسمية صفحاتك لتضيف page. إلى امتداداتها (غير اسم الملف MyPage.tsx مثلًا إلى MyPage.page.tsx)

ملاحظة: تأكد من إعادة تسمية الملفات document.js_ و app.js_ و middleware.js بالإضافة إلى الملفات الموجودة ضمن pages/api.

شبكة إيصال المحتوى CDN مع الخيار assetPrefix

تنبيه: يهيئ النشر على Vercel تلقائيًا شبكة إيصال محتوى CDN لمشروع Next.js. لا حاجة لإعداد الخيار assetPrefix يدويًا.

ملاحظة: تدعم Next.js ابتداءً من النسخة +9.5 تخصيص مسار أساسي للتطبيق، وهذا يلائم استضافة تطبيقك ضمن مسار فرعي مثل docs/. لا حاجة في هذه الحالة إلى استخدام assetPrefix.

لإعداد شبكة إيصال محتوى CDN يمكنك إعداد بادئة خاصة بالموجودات asset prefix وتهيئة أصل شبكة CDN لتحلل النطاق الذي يستضيف تطبيق Next.js. افتح الملف next.config.js وأضف الإعداد assetPrefix:

const isProd = process.env.NODE_ENV === 'production'

module.exports = {
  // استخدم شبكة إيصال المحتوى عند الإنتاج و الخادم المحلي عند التطوير
  assetPrefix: isProd ? 'https://cdn.mydomain.com' : '',
}

تستخدم تلقائيًا بادئة الموجودات لملفات JavaScript و CSS والتي تُحمَّل من المسار /_next/ (المجلد /next/static.). سيصبح الطلب التالي:

/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js

لجزء من شيفرة JS مثلًا وفق الإعداد السابق كما يلي:

https://cdn.mydomain.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js


ستعتمد التهيئة الدقيقة لرفع ملفاتك إلى منظومة ايصال محتوى على المنظومة التي تختارها، وسيكون المجلد الوحيد الذي عليك استضافته على منظومة إيصال المحتوى هو محتوى المجلد .next/static/ الذي ينبغي رفعه ليكون على الشكل /next/static_ كما يشير طلب URL. لا ترفع بقية ملفات المجلد /next. فمن الخطأ عرض شيفرة الخادم وبقية الإعدادات للعموم.

على الرغم من أنّ الإعداد assetPrefix يغطي الطلبات إلى /next/static_ لكنه لا يؤثر بالمسارات التالية:

  • الملفات في المجلد، فإن أردت تخديم هذه الموجودات عبر CDN، عليك تقديم البادئة بنفسك.
  • طلبات /next/data/_ إلى صفحات getServerSideProps. إذ توجّه هذه الطلبات إلى النطاق الأساسي لكونها غير ساكنة.
  • طلبات /next/data/_ إلى صفحات getStaticProps. إذ توجّه هذه الطلبات إلى النطاق الأساسي لدعم التوليد التدريجي الساكن حتى لو لم تكن تستخدمه (لأغراض الترابط).

الإعدادات المخصصة لمحزّم Webpack

ملاحظة: لم تُغطى التغييرات في إعداد webpack بواسطة مخططات الإصدار SemVer لهذا تابع على مسؤوليتك الشخصية.

قبل أن تتابع بإضافة إعدادات webpack مخصصة إلى تطبيقك تأكد أولًا من عدم دعم Next.js للحالة التي تواجهك:

تتاح بعض الميزات التي تُطلب بكثرة على شكل إضافات:

لكي توسّع طريقة استخدامنا لمحزّم webpack، بإمكانك تعريف دالة توسّع إعداداته ضمن الملف next.config.js كالتالي:

module.exports = {
  webpack: (
    config,
    { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }
  ) => {
    // Important: return the modified config
    return config
  },
}

تُنفَّذ الدالة webpack مرتين، الأولى للخادم والثانية للعميل. يتيح لك ذلك التمييز بين إعدادات الخادم والعميل من خلال الخاصية isServer

إن الوسيط الثاني للدالة webpack هو كائن له الخاصيات التالية:

  • buildId: من النوع string، معرّف البناء، ويُستخدم كمعرّف فريد بين نسخ البناء.
  • dev: قيمة منطقية تشير إلى أنّ التصريف سيجري في بيئة التطوير أم لا.
  • isServer: قيمة منطقية true إن كان التصريف من جانب الخادم و false من جانب العميل.
  • nextRuntime: من أحد النوعين String | undefined، وتحدد منصة تنفيذ الشيفرة عندما يجري التصريف من جانب الخادم وتاخذ إحدى القيمتين "edge" أو "nodejs" بينما تأخذ القيمة undefined عند التصريف من جانب العميل.
  • defaultLoaders: من النوع object، وتشير إلى المحمّلات الافتراضية في تستخدم داخليًا في Next.js:
    • babel: من النوع object وتحمّل تهيئة babel-loader

إليك مثال عن استخدام defaultLoaders.babel:

//babel-loader مثال عن إضافة محمّل يعتمد على 
// @next/mdx المصدر مأخوذ من الإضافة 
// https://github.com/vercel/next.js/tree/canary/packages/next-mdx
module.exports = {
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.mdx/,
      use: [
        options.defaultLoaders.babel,
        {
          loader: '@mdx-js/loader',
          options: pluginOptions.options,
        },
      ],
    })

    return config
  },
}

الخاصية nextRuntime

تأخذ isServer القيمة true عندما تكون قيمة nextRuntime هي "edge" أو "nodejs". وتستخدم الحالة التي تكون فيها قيمة nextRuntime هي edge حاليًا مع الأداة الوسطية ومكوّنات الخادم في منصة التشغيل الحدودية فقط.

ضغط المحتوى

تستخدم Next.js تقنية gzip لضغط المحتوى المصيّر والملفات الساكنة. ستحتاج عمومًا لتفعيل الضغط على خوادم HTTP الوكيلة مثل nginx لتفريغ حمولة عملية Node.js.

لإلغاء الضغط، افتح الملف next.config.js وعطّل الخيار compress:

module.exports = {
  compress: false,
}

إعداد مرحلة التشغيل

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

لإضافة إعدادات زمن التشغيل إلى التطبيق، افتح الملف next.config.js وأضف الإعدادين publicRuntimeConfig و serverRuntimeConfig:

module.exports = {
  serverRuntimeConfig: {
    // يُتاح فقط من جانب الخادم
    mySecret: 'secret',
    secondSecret: process.env.SECOND_SECRET, // يُمرر عبر متغيرات البيئة
  },
  publicRuntimeConfig: {
    // سيكون متاحًا في جانبي الخادم والعميل
    staticFolder: '/static',
  },
}

ضع أية إعدادات تتعلق بزمن التشغيل على الخادم ضمنserverRuntimeConfig، وأية إعدادات يمكن الوصول إليها في جانبي الخادم و العميل ضمن publicRuntimeConfig.

إن اعتمدت الصفحة على publicRuntimeConfig فيجب أن تستخدم getInitialProps أو getServerSideProps أو يجب أن يمتلك التطبيق مكوّن App مخصص مع getInitialProps كي لا تستخدم التحسين التقائي الساكن. لا يمكن استخدام إعدادات زمن التشغيل في أي صفحة أو مكوّن ما لم يُصيّر في جانب الخادم.

للوصول إلى إعدادات زمن التشغيل في تطبيقك، استخدم next/config كالتالي:

import getConfig from 'next/config'
import Image from 'next/image'
//publicRuntimeConfig و serverRuntimeConfig يضم فقط 
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
// يتاح فقط في جانب الخادم
console.log(serverRuntimeConfig.mySecret)
// يُتاح في كلا الجانبين
console.log(publicRuntimeConfig.staticFolder)

function MyImage() {
  return (
    <div>
      <Image
        src={`${publicRuntimeConfig.staticFolder}/logo.png`}
        alt="logo"
        layout="fill"
      />
    </div>
  )
}

export default MyImage

تعطيل الترويسة x-powered-by

تضيف Next.js الترويسة x-powered-by افتراضيًا. لإلغاء الأمر، افتح الملف next.config.js وعطّل الإعداد poweredByHeader:

module.exports = {
  poweredByHeader: false,
}

تعطيل ترويسة الاستجابة ETag

تولّد Next.js الترويسة etags لكل صفحة افتراضيًا، وقد ترغب في تعطيل هذه الميزة في بعض الصفحات وفقًا للاستراتيجية التي تستخدمها في التخزين المؤقت، لهذا، افتح الملف next.config.js وعطّل الإعداد generateEtags:

module.exports = {
  generateEtags: false,
}

تعطيل تحديد مدة الإبقاء على اتصال HTTP

توائم Next.js آلية إحضار البيانات في node لهذا تمكّن تحديد مدة الإبقاء على اتصال HTTP افتراضيًا HTTP Keep-Alive. إن كنت ترغب في تعطيل هذا الإعداد لاستدعاء معين مفرد للدالة ()fetch أضف الخيار Agent كالتالي:

import { Agent } from 'https'

const url = 'https://example.com'
const agent = new Agent({ keepAlive: false })
fetch(url, { agent })

ولإلغاء جميع استدعاءات ()fetch عمومًا استخدم ملف التهيئة next.config.js:

module.exports = {
  httpAgentOptions: {
    keepAlive: false,
  },
}

إعداد مجلد بناء مخصص

يمكن اختيار الاسم الذي تريده لمجلد بناء مخصص بدلًا من استخدام next.. فإن أردت ذلك، افتح الملف next.config.js وأضف الإعداد distDir:

module.exports = {
  distDir: 'build',
}

إن نفّذت الآن الأمر next build، ستستخدم Next.js المجلد build بدلًا من المجلد next. الافتراضي.

إعداد معرّف فريد للنسخ

تستخدم Next.js معرّفًا ثابتًا فريدًا ID أثناء بناء التطبيق لتحديد الإصدار الذي يُخدّم من تطبيقك. قد يسبب هذا الأمر المشاكل عند نشر التطبيق على عدة خوادم ثم يُنفَّذ الأمر next build على كل خادم. ولكي تحافظ على معرّف النسخ على جميع الخوادم يمكنك تحديد معرّفًا خاصًا بك للنسخة.

لتنفيذ الأمر، افتح الملف next.config.js وأضف الدالة generateBuildId:

module.exports = {
  generateBuildId: async () => {
    // You can, for example, get the latest git commit hash here
    return 'my-build-id'
  },
}

إعداد الخيار onDemandEntries

تعرض بعض الخيارات التي تتيح لك التحكم بكيفية تخلص الخادم أو إبقائه لبعض الصفحات المبنية في الذاكرة أثناء التطوير.

لتغيير الإعداد الافتراضي، افتح الملف next.config.js وأضف الإعداد onDemandEntries:

module.exports = {
  onDemandEntries: {
    //(ميلي ثانية) الفترة التي التي يبقي فيها الخادم الصفحات في الذاكرة
    maxInactiveAge: 25 * 1000,
    // عد الصفحات التي يجب إبقاؤها معًا دون أن تُلغى
    pagesBufferLength: 2,
  },
}

تجاهل المدقق ESLint

عندما تكتشف Next.js وجود ESLint في تطبيقك فإنها تُحبط عملية البناء (next build) عند وجود أي خطأ مصدره المدقق ESLint. لكن إن أردت من Next.js أن تبني نسخة إنتاج حتى لو وجودت أخطاء مصدرها ESLint، يمكنك تعطيل خطوة التدقيق المدمجة بالكامل. لا ننصحك بهذا الخيار إلا إن كنت قد هيأت مسبقًا ESLint ليعمل ضمن مرحلة أخرى من مراحل عملك (كمرحلة التكامل المتواصل CI).

لتعطيل ESLint، افتح الملف next.config.js ومكِّن الخيار ignoreDuringBuilds ضمن الإعداد eslint:

module.exports = {
  eslint: {
    // ESLint يسمح ذلك بنجاح بناء التطبيق حتى لو كانت هناك أخطاء في المدقق
    ignoreDuringBuilds: true,
  },
}

تجاهل أخطاء TypeScript

تُحبط Next.js عملية البناء (next build) عند وجود أي خطأ مصدره TypeScript. لكن إن أردت من Next.js أن تبني نسخة إنتاج حتى لو وجودت أخطاء مصدرها TypeScript (وهذا أمر خطر)، يمكنك تعطيل خطوة التحقق المدمجة من أخطاء TypeScript بالكامل.

تأكد عند تعطيل تلك الخطوة، أنك ستتحقق من الأنواع خلال عملية البناء أو النشر، وإلا كان عملك محفوفًا بالمخاطر.

افتح الملف next.config.js ومكّن الخيار ignoreBuildErrors ضمن الإعداد typescript

module.exports = {
  typescript: {
    // !! تحذير !!
   // Typescript يسمح ذلك بنجاح بناء التطبيق حتى لو كانت هناك أخطاء في 
    ignoreBuildErrors: true,
  },
}

استخدام الإعداد exportPathMap

هذه الميزة محصورة باستخدام next export. عد إلى الصفحة "تصدير التطبيق إلى صفحات HTML ساكنة" من هذا التوثيق.

يتيح لك هذا الإعداد تحديد علاقة بين مسارات الطلب وصفحات الوجهة، كي تُستخدم أثناء التصدير. تُتاح المسارات المعرّفة داخل الإعداد exportPathMap أيضًا عند تنفيذ الأمر next dev.

لنبدأ بمثال بسيط لإنشاء شبكة علاقات exportPathMap مخصصة لتطبيق يضم الصفحات التالية:

  • pages/index.js
  • pages/about.js
  • pages/post.js

افتح الملف وأضف الشيفرة التالية ضمن الإعداد exportPathMap:

module.exports = {
  exportPathMap: async function (
    defaultPathMap,
    { dev, dir, outDir, distDir, buildId }
  ) {
    return {
      '/': { page: '/' },
      '/about': { page: '/about' },
      '/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } },
      '/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } },
      '/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } },
    }
  },
}

ملاحظة: لا يمكن أن تستخدم الحقل query من exportPathMap مع الصفحات الساكنة المحسّنة تلقائيًا أو مع صفحات getStaticProps لأنها تُصيَّر إلى ملفات HTML أثناء البناء، ولا يمكن الحصول على معلومات استعلام إضافية خلال التصدير next export. تُصدّر الصفحات بعد ذلك إلى ملفات HTML، إذ تصبح الصفحة about/ مثلًا /about.html/.


إن exportPathMap هي دالة غير متزامنة تقبل وسيطين أولهما defaultPathMap ويمثّل مخطط ارتباط Next.js الافتراضي، أما الوسيط الثاني فهو كائن له الخاصيات التالية:

  • dev: يأخذ القيمة true عندما تُستدعى exportPathMap في بيئة التطوير و false عند تنفيذ next export. يُستخدم exportPathMap في بيئة التطوير لتعريف الوجهات.
  • dir: المسار المطلق إلى مجلد المشروع.
  • outDir: المسار المطلق إلى المجلد /out وستكون قيمته null عندما تكون قيمة dev هي true.
  • distDir: المسار المطلق إلى المجلد /next. (يُهيّأ من خلال الإعداد distDir).
  • buildId: معرّف النسخة الفريد ID.

إن الكائن المعاد هو مخطط ارتباط مع الصفحات مفاتيحه key هي أسماء المسارات pathname وقيمه value كائنات تقبل الحقول التالية:

  • page: من النوع String، وهي الصفحة التي تُصيّر ضمن المجلد pages.
  • query: من النوع Object، وهو الكائن query الممرر إلى getInitialProps عند التصيير، وقيمته الافتراضية {}.

يمكن أن يكون اسم المسار pathname المعروض اسم ملف (مثل readme.md/)، لكن قد يكون عليك ضبط ترويسة نوع المحتوى Content-Type على القيمة text/html إن كان محتواه مختلفًا عن HTML.

تخصيص مجلد للخرج

يستخدم الأمر next export المجلد out افتراضيًا لوضع ملفات الخرج فيه، بإمكانك تخصيص مجلد آخر لهذه الغاية باستخدام الوسيط o- كالتالي:

next export -o outdir

محرف "/" الزائد

تعيد Next.js توجيه عناوين url التي تستخدم محرف / زائد إلى نظيراتها التي لا تستخدم هذا المحرف. إن سيُعاد توجيه /about/ إلى about/ مثلًا. بإمكانك تغيير هذا السلوك إلى السلوك المعاكس وذلك بفتح الملف next.config.js وإضافة الإعداد trailingSlash كالتالي:

module.exports = {
  trailingSlash: true,
}

هكذا سيُعاد توجيه العنوان about/ إلى /about/.

النمط الصارم في React

اقتراح: نقترح بشدة استخدام هذا الوضع في تطبيقات Next.js لتحضير تطبيقك بشكل أفضل لإصدارت React المستقبلية.

إن النمط الصارم في React هو وضع تطوير فقط، كي يشير إلى الأخطاء المحتملة في التطبيق. إذ يفيدك في تحديد دورات العمل غير الآمنة واستخدام الواجهات البرمجية الموروثة وغيرها من الميزات الأخرى. تدعم Next.js النمط الصارم في مرحلة زمن التشغيل، ولكي تستخدمه، اضبط الخيار التالي في الملف next.config.js:

// next.config.js
module.exports = {
  reactStrictMode: true,
}

إن لم تكن مستعدًا مع فريقك للعمل مع هذا النمط، لا بأس بذلك! يمكنك الانتقال إليه تدريجيًا صفحة صفحة باستخدام <React.StrictMode>.

إدراج وحدات خارجية من خلال عناوين URL

إن الإدراج من خلال عناوين URL هي ميزة تجريبية تسمح بإدراج وحدات برمجية مباشرة من خوادم خارجية (بدلًا من القرص المحلي).

تنبيه: هذه الميزة تجريبية، لهذا استخدم النطاقات التي تثق بها فقط لتزيل وتنفيذ الشيفرة على جهازك. استخدم الميزة بتروٍ وحذر حتى نشير إلى هذه الميزة على أنها مستقرة stable.

لاستخدام الميزة، أضف العناوين المسموحة إلى الإعداد urlImports في الملف next.config.js:

module.exports = {
  experimental: {
    urlImports: ['https://example.com/modules/'],
  },
}

بإمكانك حينها إدراج الوحدات من عنوان URL مباشرة في تطبيقك:

import { a, b, c } from 'https://example.com/modules/some/module.js'

يمكن استخدام هذه الميزة في أي مكان يمكن فيه إدراج حزمة اعتيادية.

الأمان

صُممت هذه الميزة ليكون الأمان أولويتها القصوى. إذ أضفنا بدايةً رايةً تجريبيةً تجبرك على التصريح بسماحك للإدراج من عنوان محدد. نعمل الآن على حصر عمل الوحدات المدرجة من عناوين خارجية ضمن صندوق التقييد sandbox في المتصفح باستخدام بيئة التشغيل الحدودية Edge Runtime التي تُستخدم من قبل الأداة الوسطية و Next.js Live.

إقفال الملفات

تُنشئ Next.js ملفًا مقفلًا في المجلد next.lock عند الإدراج من عناوين URL. يُفترض أن يُدفع هذا المجلد إلى Git ولا ينبغي تضمينه في الملف gitignore.

  • عند تنفيذ الأمر next dev، تنزّل Next.js وتضيف كل الوحدات المدرجة من عناوين URL إلى الملف المقفل.
  • عند تنفيذ الأمر next build، تستخدم Next.js الملف المقفل فقط لبناء التطبيق لمرحلة الإنتاج.

لا حاجة عادة إلى أية طلبات شبكة وسيسبب أي ملف مقفل منتهي الصلاحية إحباط البناء. أما الاستثناء الوحيد لهذه الحالة فهي الموارد التي تستجيب بالترويسة Cache-Control: no-cache إذ لا تمتلك هذه الموارد المدخل no-cache في الملف المقفل، وبالتالي لا بد من إحضارها عند كل عملية بناء.

أمثلة

إدراج Skypack

import confetti from 'https://cdn.skypack.dev/canvas-confetti'
import { useEffect } from 'react'

export default () => {
  useEffect(() => {
    confetti()
  })
  return <p>Hello</p>
}

إدراج صور ساكنة

import Image from 'next/image'
import logo from 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png'

export default () => (
  <div>
    <Image src={logo} placeholder="blur" />
  </div>
)

عناوين ضمن CSS

.className {
  background: url('https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png');
}

إدراج ملفات ساكنة

import Image from 'next/image'

const logo = new URL(
  'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png',
  import.meta.url
)

export default () => <div>{logo.pathname}</div>

مؤشر بناء التطبيق

عندما تحرر الشيفرة وتصرّف التطبيق في Next.js، سيظهر مؤشر التصريف في أسفل يمين الصفحة.

ملاحظة: يظهر هذا المؤشر في وضع التطوير فقط ولن يظهر أثناء بناء وتشغيل التطبيق في نمط الإنتاج.

قد يتوضع هذا المؤشر في مكان خاطئ ضمن الصفحة، فإن أردت تغيير موضعه، افتح الملف next.config.js واضبط قيمة الخيار buildActivityPosition ضمن الكائن devIndicators على bottom-right (افتراضي)، أو bottom-left أو top-right أو top-left:

module.exports = {
  devIndicators: {
    buildActivityPosition: 'bottom-right',
  },
}

وقد لا يفيدك هذا المؤشر في بعض الحالات، لذلك بالإمكان إزالته بتعطيل الإعداد buildActivity ضمن الكائن devIndicators:

module.exports = {
  devIndicators: {
    buildActivity: false,
  },
}

أمثلة

المصادر