الفرق بين المراجعتين ل"Next.js/next.config.js"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
سطر 121: سطر 121:
  
 
export default Home
 
export default Home
 +
</syntaxhighlight>
 +
 +
== إعادة الكتابة في Next.js ==
 +
تتيح لك هملية إعادة الكتابة Rewrites ربط مسار طلب وارد إلى مسار وجهة أخرى. تعمل إعادة الكتابة مثل وسيط لعناوين URL وتقنِّع المسار إلى الوجهة ليظهر المستخدم وكأنه لم يغير مكانه في الموقع. بالمقابل تعيد عملية إعادة التوجيه redirects توجيه المستخدم إلى صفحة جديدة وتُظهر التغييرات على العنوان.
 +
 +
ولتستخدم إعادة الكتابة أضف المفتاح  <code>rewrites</code> إلى ملف التهيئة <code>next.config.js</code>:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/about',
 +
        destination: '/',
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>تُطبق إعادة الكتابة عن التوجّه في جانب العميل، وتطبق إعادة الكتابة في المثال السابق مثلًا على العنصر <code><"Link href="/about></code>.
 +
 +
إن الدالة هي دالة غير متزامنة تتوقع غعادة مصفوفة تضم كائنات تمتلك الخاصيتين <code>source</code> و <code>destination</code>:
 +
 +
* <code>source</code>: من النوع <code>String</code>، وهو نموذج مسار الطلب الوارد.
 +
* <code>destination</code>: من النوع <code>String</code>، وهو المسار الذي تريد التوجه إليه.
 +
* <code>basePath</code>: تأخذ أحد القيمتين <code>false</code> أو <code>undefined</code>، فإن كانت القيمة <code>false</code> ، لن يضاف المسار الأساسي إلى العنوان عند المطابقة، ويستخد في إعادة الكتابة الخارجية.
 +
* <code>locale</code>: تأخذ أحد القيمتين <code>false</code> أو <code>undefined</code>، يحدد إن كان سُضاف الإعداد المحلي إلى المسار عند المطابقة.
 +
* <code>has</code>: مصفوفة من الكائنات <code>has objects</code> لها الخاصيات <code>type</code> و <code>key</code> و <code>value</code>.
 +
 +
تجري عملية إعادة الكتابة بعد التحقق من منظومة الملفات (ملفات المجلدين <code>pages</code> و <code>public</code>) وقبل التوجه الديناميكي افتراضيًا. يمكن تغيير هذا السلوك بإعادة كائن بدلًا من المصفوفة من الدالة <code>rewrites</code> ابتداءً من النسخة <code>v10.1</code> من Next.js:<syntaxhighlight lang="javascript">
 +
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*`,
 +
        },
 +
      ],
 +
    }
 +
  },
 +
}
 +
</syntaxhighlight>'''ملاحظة''': إن إجراء إعادة كتابة ضمن <code>beforeFiles</code> لا يتضمن التحقق المباشر من منظومة الملفات والوجهات الديناميكية بعد مطابقة المصدر، بل تستمر حتى يجري التحقق من جميع الإجراءات ضمن <code>beforeFiles</code>. إليك ترتيب التحقق من وجهات  Next.js:
 +
 +
# يجري التحقق من الترويسات headers أو تطبيقها.
 +
# يجري التحقق من عمليات إعادة التوجيه redirects أو تطبيقها.
 +
# يجري التحقق من عمليات إعادة الكتابة ضمن <code>beforeFiles</code> أو تطبيقها.
 +
# يجري التحقق من الملفات الساكنة في المجلد <code>public</code> و <code>next/static_</code> والصفحات غير الديناميكية أو تخديمها.
 +
# يجري التحقق من عمليات إعادة الكتابة ضمن <code>afterFiles</code> أو تطبيقها، فإن تطابقت إحدى عمليات إعادة الكتابة، نتحقق من الوجهة الديناميكية أو الملفات الساكنة بعد كل تطابق.
 +
# يجري التحقق من إعادة الكتابة ضمن <code>fallback</code> أو تطبيقها، ويجري تطبيقها قبل تصيير الصفحة 404، وبعد التحقق من الوجهات الديناميكية وجميع الموجودات الساكنة.
 +
 +
=== معاملات الدالة <code>rewrites</code> ===
 +
عند استخدام المعاملات، تُمرر هذه المعاملات إلى الدالة عبر الاستعلام افتراضيًا إن لم يُستخدم أيًا منها في الخاصية <code>destination</code>:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/old-about/:path*',
 +
        destination: '/about', //وبالتالي سيُمرر path لم يُستخدم المعامل
 +
          // تلقائيًا ضمن الاستعلام
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>أما إن استُخدم معامل في الوجهة <code>destination</code>، فلن يُمرر أي من المعاملات تلقائيًا ضمن الاستعلام:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/docs/:path*',
 +
        destination: '/:path*',//وبالتالي لن يُمرر path استُخدم المعامل
 +
      // أي معامل تلقائيًا من خلال الاستعلام
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>لكن لا يزال بإمكانك تمرير المعاملات يدويًا ضمن الاستعلام إن استُخدم أحدها في الخاصية  <code>destination</code>:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/:first/:second',
 +
        destination: '/:first?second=:second',
 +
         
 +
        //قد استُخدم في الوجهة فلن يُضاف  first طالما أن المعامل
 +
        // تلقائيًا  second المعامل الثاني
 +
        // إلى الاستعلام على الرغم من إمكانية إضافته يدويًا كما في الأعلى
 +
         
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>'''ملاحظة''': تُحلَّل معاملات إعادة الكتابة للصفحات الساكنة الناتجة عن [[Next.js/automatic static optimization|التحسين التقائي الساكن]] أو [[Next.js/data fetching|التصيير المسبق]] من جانب العميل بعد الترطيب ويُزوَّد به الاستعلام.
 +
 +
=== مطابقة المسارات ===
 +
يُسمح بمطابقة المسارات كأن يُطابق المسار <code>blog/:slug/</code> المسار <code>blog/hello-world/</code> (دون تداخل في المسارات).<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/blog/:slug',
 +
        destination: '/news/:slug', // يمكن مطابقة المعاملات هنا
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>
 +
 +
==== مطابقة المسارات بوجود محارف بديلة ====
 +
لمطابقة مسار باستخدام محارف بديلة، بإمكانك استعمال <code>*</code> بعد المعامل، إذ سيُطابق المسار <code>/blog/:slug*</code> مثلًا المسار <code>/blog/a/b/c/d/hello-world</code>:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/blog/:slug*',
 +
        destination: '/news/:slug*', // يمكن مطابقة المعاملات هنا
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>
 +
 +
==== مطابقة المسارات من خلال التعابير النمطية Regex ====
 +
لمطابقة مسار من خلال التعابير النمطية، غلِّف التعبير النمطي ضمن قوسين بعد المعامل. سيطابق المسار <code>blog/:slug(\\d{1,})/</code> مثلًا المسار <code>blog/123/</code> وليس المسار <code>blog/abc/</code>:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/old-blog/:post(\\d{1,})',
 +
        destination: '/blog/:post', // يمكن مطابقة المعاملات هنا
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>تُستخدم المحارف التالية <code>(</code> و <code>)</code> و <code>{</code> و <code>}</code> و <code>:</code> و <code>*</code> و <code>+</code> و <code>?</code> في صياغة التعابير النمطية المستخدمة لمطابقة المسارات، فإن استُخدمت في الخاصية <code>source</code> كقيم غير خاصة non-special لا بد من تجاوزها بإضافة الصيغة قبلها:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        //`/english(default)/something` سيثطابق ذلك 
 +
        source: '/english\\(default\\)/:slug',
 +
        destination: '/en-us/:slug',
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>
 +
 +
=== مطابقة الترويسات وملفات تعريف الارتباط والاستعلام ===
 +
بالإمكان مطابقة عملية إعادة كتابة عندما تُطابِق قيم الترويسات أو ملف تعريف الارتباط أو الاستعلام قيمة الحقل <code>has</code> فقط. أي يجب أن تتطابق الخاصية <code>source</code> وجميع عناصر <code>has</code> حتى تُطبَّق إعادة الكتابة. تمتلك العناصر <code>has</code> الحقول التالية:
 +
 +
* <code>type</code>: من النوع <code>String</code>، وسيكون إما <code>header</code> أو  <code>cookie</code> أو  <code>host</code> أو  <code>query</code>.
 +
* <code>key</code>: من النوع <code>String</code>، مفتاح النوع المختار <code>type</code> الذي سيُطابق.
 +
* <code>value</code>: من النوع <code>String</code> أو <code>undefined</code>، القيمة التي سيجري التحقق منها، وإن كانت غير محددة ستُطابق أي قيمة. يمكن استخدام سلسلة نصية تشابه التعبير النمطي لالتقاط جزء محدد من القيمة. فإن استُخدمت القيمة <code>first-(?<paramName>.*)</code> لمطابقة <code>first-second</code>، ستتمكن حينها من استخدام <code>second</code> في الوجهة مع المعامل <code>:paramName</code>.
 +
<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      // `x-rewrite-me` إن وجدت الترويسة
 +
      // تُطبق إعادة الكتابة
 +
     
 +
      {
 +
        source: '/:path*',
 +
        has: [
 +
          {
 +
            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',
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>
 +
 +
=== إعادة الكتابة إلى عنوان URL خارجي ===
 +
بإمكانك إعادة الكتابة إلى عنوان URL خارجي، وهذا مفيد لتبني Next.js تدريجيًا. إليك مثالًا عن إعادة كتابة لتحويل الوجهة <code>blog/</code> لتطبيقك الرئيسي إلى موقع خارجي:<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/blog',
 +
        destination: 'https://example.com/blog',
 +
      },
 +
      {
 +
        source: '/blog/:slug',
 +
        destination: 'https://example.com/blog/:slug',
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>إن كنت تستخدم الحقل <code>trailingSlash: true</code> فستحتاج أيضًا إلى حشر محرف <code>/</code> في نهاية قيمة المعامل <code>source</code>. وإن كان الخادم الهدف يتوقع وجود المحرف <code>/</code> في نهاية القيمة، فلا بد من وضعها في المعامل <code>destination</code> أيضًا.<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  trailingSlash: true,
 +
  async rewrites() {
 +
    return [
 +
      {
 +
        source: '/blog/',
 +
        destination: 'https://example.com/blog/',
 +
      },
 +
      {
 +
        source: '/blog/:path*/',
 +
        destination: 'https://example.com/blog/:path*/',
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>
 +
 +
==== تبني Next.js تدريجيًا ====
 +
يمكنك دفع Next.js للتراجع كي تلعب دور الوسيط لموقع ويب موجود بعد التحقق من كل وجهات Next.js. لا حاجة هكذا لتغيير إعدادات إعادة الكتابة عند نقل صفحات أكثر إلى Next.js.<syntaxhighlight lang="javascript">
 +
module.exports = {
 +
  async rewrites() {
 +
    return {
 +
      fallback: [
 +
        {
 +
          source: '/:path*',
 +
          destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`,
 +
        },
 +
      ],
 +
    }
 +
  },
 +
}
 +
</syntaxhighlight>لمزيد من المعلومات راجع الصفحة "الانتقال إلى Next.js" من هذا [[Next.js/migrating|التوثيق]].
 +
 +
==== إعادة الكتابة مع دعم للمسار الأساسي ====
 +
عند استخدام <code>basePath</code> مع إعادة الكتابة ستُضاف تلقائيًا البادئة إلى المعاملين <code>source</code> و <code>destination</code> ما لم تضف الإعداد <code>basePath: false</code> إلى دالة إعادة الكتابة:<syntaxhighlight lang="javascript">
 +
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,
 +
      },
 +
    ]
 +
  },
 +
}
 +
</syntaxhighlight>
 +
 +
==== إعادة الكتابة مع دعم التوجه i18n ====
 +
عند استخدام <code>i18n</code> مع إعادة الكتابة ستُضاف تلقائيًا البادئة المهيأة مسبقًا <code>locales</code> إلى المعاملين <code>source</code> و <code>destination</code> ما لم تضف الإعداد <code>locale: false</code> إلى دالة إعادة الكتابة، وفي حال استخدامه لا بد من إضافة تلك البادئة يدويًا حتى تُطابق بشكل صحيح:<syntaxhighlight lang="javascript">
 +
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',
 +
      },
 +
    ]
 +
  },
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  

مراجعة 18:16، 11 يوليو 2022

بإمكانك إنشاء الملف 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.

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

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

لإضافة متغيرات بيئة إلى تجميعة 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>

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

بإمكانك استخدام الإعداد 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

إعادة الكتابة في Next.js

تتيح لك هملية إعادة الكتابة 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.

تجري عملية إعادة الكتابة بعد التحقق من منظومة الملفات (ملفات المجلدين pages و public) وقبل التوجه الديناميكي افتراضيًا. يمكن تغيير هذا السلوك بإعادة كائن بدلًا من المصفوفة من الدالة rewrites ابتداءً من النسخة v10.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، وبعد التحقق من الوجهات الديناميكية وجميع الموجودات الساكنة.

معاملات الدالة 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 فقط. أي يجب أن تتطابق الخاصية source وجميع عناصر has حتى تُطبَّق إعادة الكتابة. تمتلك العناصر has الحقول التالية:

  • 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',
      },
      // إن تطابقت الترويسة أو ملف الارتباط أو الاستعلام 
      // تُطبق إعادة الكتابة
      
      {
        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',
      },
    ]
  },
}

المصادر