الانتقال من Vite

سيساعدك هذا الدليل في نقل تطبيق Vite موجود إلى Next.js.

لماذا التبديل؟

هناك عدة أسباب قد تدفعك للتبديل من Vite إلى Next.js:

  1. بطء وقت تحميل الصفحة الأولي: إذا قمت ببناء تطبيقك باستخدام إضافة Vite الافتراضية لـ React، فإن تطبيقك هو تطبيق يعمل فقط من جانب العميل. التطبيقات التي تعمل فقط من جانب العميل، والمعروفة أيضًا باسم تطبيقات الصفحة الواحدة (SPAs)، غالبًا ما تعاني من بطء في وقت تحميل الصفحة الأولي. يحدث هذا لعدة أسباب:
    1. يحتاج المتصفح إلى انتظار تنزيل وتشغيل كود React وحزمة التطبيق بالكامل قبل أن يتمكن الكود من إرسال طلبات لتحميل بعض البيانات.
    2. ينمو كود التطبيق الخاص بك مع كل ميزة جديدة واعتماد إضافي تضيفه.
  2. عدم وجود تقسيم تلقائي للكود: يمكن إدارة مشكلة بطء أوقات التحميل السابقة إلى حد ما مع تقسيم الكود. ومع ذلك، إذا حاولت إجراء تقسيم الكود يدويًا، فغالبًا ما ستجعل الأداء أسوأ. من السهل إدخال شلالات الشبكة عن غير قصد عند تقسيم الكود يدويًا. يوفر Next.js تقسيم الكود التلقائي المدمج في جهاز التوجيه الخاص به.
  3. شلالات الشبكة: أحد الأسباب الشائعة لضعف الأداء يحدث عندما تقوم التطبيقات بعمل طلبات متتابعة بين العميل والخادم لجلب البيانات. أحد الأنماط الشائعة لجلب البيانات في SPA هو عرض عنصر نائب في البداية، ثم جلب البيانات بعد تحميل المكون. لسوء الحظ، هذا يعني أن المكون الفرعي الذي يجلب البيانات لا يمكنه بدء الجلب حتى ينتهي المكون الأصلي من تحميل بياناته الخاصة. في Next.js، تم حل هذه المشكلة عن طريق جلب البيانات في مكونات الخادم.
  4. حالات التحميل السريعة والمقصودة: بفضل الدعم المدمج لـ البث مع Suspense، مع Next.js، يمكنك أن تكون أكثر تحديدًا لأي أجزاء من واجهة المستخدم تريد تحميلها أولًا وبأي ترتيب دون إدخال شلالات الشبكة. هذا يمكّنك من بناء صفحات يتم تحميلها بشكل أسرع وأيضًا القضاء على تحولات التخطيط.
  5. اختيار استراتيجية جلب البيانات: اعتمادًا على احتياجاتك، يسمح لك Next.js باختيار استراتيجية جلب البيانات على أساس الصفحة والمكون. يمكنك أن تقرر الجلب في وقت البناء، أو في وقت الطلب على الخادم، أو على العميل. على سبيل المثال، يمكنك جلب البيانات من نظام إدارة المحتوى الخاص بك وعرض مشاركات مدونتك في وقت البناء، والتي يمكن بعد ذلك تخزينها بكفاءة على CDN.
  6. البرمجيات الوسيطة: برمجيات Next.js الوسيطة تتيح لك تشغيل الكود على الخادم قبل اكتمال الطلب. هذا مفيد بشكل خاص لتجنب ظهور محتوى غير مصادق عليه عندما يزور المستخدم صفحة تتطلب مصادقة عن طريق توجيه المستخدم إلى صفحة تسجيل الدخول. البرمجيات الوسيطة مفيدة أيضًا للتجربة والتوطين.
  7. تحسينات مدمجة: غالبًا ما يكون للصور والخطوط والنصوص البرمجية لجهات خارجية تأثير كبير على أداء التطبيق. يأتي Next.js مع مكونات مدمجة تقوم بتحسينها تلقائيًا لك.

خطوات النقل

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

الخطوة 1: تثبيت اعتماد Next.js

أول شيء تحتاج إلى فعله هو تثبيت next كاعتماد:

Terminal
npm install next@latest

الخطوة 2: إنشاء ملف تكوين Next.js

قم بإنشاء ملف next.config.mjs في جذر مشروعك. سيحتوي هذا الملف على خيارات تكوين Next.js.

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // يخرج تطبيق صفحة واحدة (SPA).
  distDir: './dist', // يغير دليل إخراج البناء إلى `./dist/`.
}

export default nextConfig

جيد أن تعرف: يمكنك استخدام إما .js أو .mjs لملف تكوين Next.js الخاص بك.

الخطوة 3: تحديث تكوين TypeScript

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

  1. قم بإزالة مرجع المشروع إلى tsconfig.node.json
  2. أضف ./dist/types/**/*.ts و ./next-env.d.ts إلى مصفوفة include
  3. أضف ./node_modules إلى مصفوفة exclude
  4. أضف { "name": "next" } إلى مصفوفة plugins في compilerOptions: "plugins": [{ "name": "next" }]
  5. اضبط esModuleInterop على true: "esModuleInterop": true
  6. اضبط jsx على preserve: "jsx": "preserve"
  7. اضبط allowJs على true: "allowJs": true
  8. اضبط forceConsistentCasingInFileNames على true: "forceConsistentCasingInFileNames": true
  9. اضبط incremental على true: "incremental": true

إليك مثال على ملف tsconfig.json يعمل مع هذه التغييرات:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
    "incremental": true,
    "plugins": [{ "name": "next" }]
  },
  "include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
  "exclude": ["./node_modules"]
}

يمكنك العثور على مزيد من المعلومات حول تكوين TypeScript في وثائق Next.js.

الخطوة 4: إنشاء تخطيط الجذر

يجب أن يتضمن تطبيق جهاز توجيه التطبيق في Next.js ملف تخطيط جذر، وهو مكون خادم React سيلف جميع الصفحات في تطبيقك. يتم تعريف هذا الملف في المستوى الأعلى من دليل app.

أقرب ما يعادل ملف تخطيط الجذر في تطبيق Vite هو ملف index.html، الذي يحتوي على علامات <html> و <head> و <body> الخاصة بك.

في هذه الخطوة، ستحول ملف index.html الخاص بك إلى ملف تخطيط جذر:

  1. قم بإنشاء دليل app جديد في دليل src الخاص بك.
  2. قم بإنشاء ملف layout.tsx جديد داخل دليل app:
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return null
}
export default function RootLayout({ children }) {
  return null
}

جيد أن تعرف: يمكن استخدام امتدادات .js أو .jsx أو .tsx لملفات التخطيط.

  1. انسخ محتوى ملف index.html الخاص بك إلى مكون <RootLayout> الذي تم إنشاؤه مسبقًا مع استبدال علامات body.div#root و body.script بـ <div id="root">{children}</div>:
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. يتضمن Next.js بالفعل بشكل افتراضي علامات meta charset و meta viewport، لذا يمكنك إزالتها بأمان من <head> الخاص بك:
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. يتم إضافة أي ملفات بيانات وصفية مثل favicon.ico و icon.png و robots.txt تلقائيًا إلى علامة <head> للتطبيق طالما أنك وضعتها في المستوى الأعلى من دليل app. بعد نقل جميع الملفات المدعومة إلى دليل app، يمكنك حذف علامات <link> الخاصة بها بأمان:
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. أخيرًا، يمكن لـ Next.js إدارة علامات <head> الأخيرة الخاصة بك باستخدام واجهة برمجة تطبيقات البيانات الوصفية. انقل معلومات البيانات الوصفية النهائية الخاصة بك إلى كائن metadata مُصدر:
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My App',
  description: 'My App is a...',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
export const metadata = {
  title: 'My App',
  description: 'My App is a...',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

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

الخطوة 5: إنشاء صفحة نقطة الدخول

في Next.js، تقوم بتعريف نقطة دخول لتطبيقك عن طريق إنشاء ملف page.tsx. أقرب ما يعادل هذا الملف في Vite هو ملف main.tsx الخاص بك. في هذه الخطوة، ستقوم بإعداد نقطة دخول التطبيق الخاص بك.

  1. قم بإنشاء دليل [[...slug]] في دليل app الخاص بك.

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

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

  1. قم بإنشاء ملف page.tsx جديد داخل دليل app/[[...slug]] بالمحتوى التالي:
'use client'

import dynamic from 'next/dynamic'
import '../../index.css'

const App = dynamic(() => import('../../App'), { ssr: false })

export default function Page() {
  return <App />
}
'use client'

import dynamic from 'next/dynamic'
import '../../index.css'

const App = dynamic(() => import('../../App'), { ssr: false })

export default function Page() {
  return <App />
}

جيد أن تعرف: يمكن استخدام امتدادات .js أو .jsx أو .tsx لملفات الصفحة.

يحتوي هذا الملف على مكون <Page> تم تمييزه كـ مكون عميل بواسطة توجيه 'use client'. بدون هذا التوجيه، كان المكون سيكون مكون خادم.

في Next.js، يتم تقديم مكونات العميل مسبقًا إلى HTML على الخادم قبل إرسالها إلى العميل، ولكن نظرًا لأننا نريد أولاً الحصول على تطبيق يعمل فقط من جانب العميل، فأنت بحاجة إلى إخبار Next.js بتعطيل التقديم المسبق لمكون <App> عن طريق استيراده ديناميكيًا مع خيار ssr مضبوطًا على false:

const App = dynamic(() => import('../../App'), { ssr: false })

الخطوة 6: تحديث استيراد الصور الثابتة

يتعامل Next.js مع استيراد الصور الثابتة بشكل مختلف قليلاً عن Vite. مع Vite، فإن استيراد ملف صورة سيعيد عنوان URL العام كسلسلة نصية:

App.tsx
import image from './img.png' // `image` سيكون '/assets/img.2d8efhg.png' في الإنتاج

export default function App() {
  return <img src={image} />
}

مع Next.js، يعيد استيراد الصور الثابتة كائنًا. يمكن بعد ذلك استخدام هذا الكائن مباشرة مع مكون <Image> الخاص بـ Next.js، أو يمكنك استخدام خاصية src للكائن مع وسم <img> الحالي.

يتمتع مكون <Image> بمزايا إضافية مثل تحسين الصور التلقائي. يقوم مكون <Image> تلقائيًا بتعيين سمات width و height لوسم <img> الناتج بناءً على أبعاد الصورة. هذا يمنع حدوث تغييرات في التخطيط عند تحميل الصورة. ومع ذلك، قد يسبب هذا مشاكل إذا كان تطبيقك يحتوي على صور يتم تصميم أحد أبعادها فقط دون الآخر ليكون auto. عندما لا يتم تعيينه إلى auto، سيعتمد البعد على قيمة سمة البعد لوسم <img>، مما قد يجعل الصورة تظهر مشوهة.

سيؤدي الاحتفاظ بوسم <img> إلى تقليل عدد التغييرات في تطبيقك ومنع المشاكل المذكورة أعلاه. ومع ذلك، سترغب لاحقًا في الانتقال إلى مكون <Image> للاستفادة من التحسينات التلقائية.

  1. تحويل مسارات الاستيراد المطلقة للصور المستوردة من /public إلى استيراد نسبي:
// قبل
import logo from '/logo.png'

// بعد
import logo from '../public/logo.png'
  1. تمرير خاصية src للصورة بدلاً من كائن الصورة بالكامل إلى وسم <img>:
// قبل
<img src={logo} />

// بعد
<img src={logo.src} />

تحذير: إذا كنت تستخدم TypeScript، فقد تواجه أخطاء نوع عند الوصول إلى خاصية src. يمكنك تجاهلها بأمان الآن. سيتم إصلاحها بنهاية هذا الدليل.

الخطوة 7: ترحيل متغيرات البيئة

يدعم Next.js متغيرات البيئة في ملفات .env بشكل مشابه لـ Vite. الفرق الرئيسي هو البادئة المستخدمة لعرض متغيرات البيئة على جانب العميل.

  • قم بتغيير جميع متغيرات البيئة ذات البادئة VITE_ إلى NEXT_PUBLIC_.

يعرض Vite بعض متغيرات البيئة المدمجة على كائن import.meta.env الخاص الذي لا يدعمه Next.js. تحتاج إلى تحديث استخدامها كما يلي:

  • import.meta.env.MODEprocess.env.NODE_ENV
  • import.meta.env.PRODprocess.env.NODE_ENV === 'production'
  • import.meta.env.DEVprocess.env.NODE_ENV !== 'production'
  • import.meta.env.SSRtypeof window !== 'undefined'

لا يوفر Next.js أيضًا متغير بيئة مدمج باسم BASE_URL. ومع ذلك، لا يزال بإمكانك تكوين واحد إذا كنت بحاجة إليه:

  1. أضف ما يلي إلى ملف .env الخاص بك:
.env
# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
  1. قم بتعيين basePath إلى process.env.NEXT_PUBLIC_BASE_PATH في ملف next.config.mjs:
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // يخرج تطبيقًا أحادي الصفحة (SPA).
  distDir: './dist', // يغير دليل إخراج البناء إلى `./dist/`.
  basePath: process.env.NEXT_PUBLIC_BASE_PATH, // يعيين المسار الأساسي إلى `/some-base-path`.
}

export default nextConfig
  1. قم بتحديث استخدامات import.meta.env.BASE_URL إلى process.env.NEXT_PUBLIC_BASE_PATH

الخطوة 8: تحديث الأوامر في package.json

يجب أن تكون الآن قادرًا على تشغيل تطبيقك لاختبار ما إذا كنت قد نجحت في ترحيله إلى Next.js. ولكن قبل ذلك، تحتاج إلى تحديث scripts في package.json بأوامر Next.js ذات الصلة، وإضافة .next و next-env.d.ts إلى .gitignore:

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
.gitignore
# ...
.next
next-env.d.ts

الآن قم بتشغيل npm run dev، وافتح http://localhost:3000. من المفترض أن ترى تطبيقك يعمل الآن على Next.js.

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

مثال: تحقق من طلب السحب هذا لمثال عملي لتطبيق Vite تم ترحيله إلى Next.js.

الخطوة 9: التنظيف

يمكنك الآن تنظيف قاعدة التعليمات البرمجية من آثار Vite ذات الصلة:

  • احذف main.tsx
  • احذف index.html
  • احذف vite-env.d.ts
  • احذف tsconfig.node.json
  • احذف vite.config.ts
  • أزل تبعيات Vite

الخطوات التالية

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