الانتقال من Vite
سيساعدك هذا الدليل في نقل تطبيق Vite موجود إلى Next.js.
لماذا التبديل؟
هناك عدة أسباب قد تدفعك للتبديل من Vite إلى Next.js:
بطء وقت تحميل الصفحة الأولي
إذا قمت ببناء تطبيقك باستخدام إضافة Vite الافتراضية لـ React، فإن تطبيقك هو تطبيق يعمل فقط على جانب العميل. التطبيقات التي تعمل فقط على جانب العميل، والمعروفة أيضًا باسم تطبيقات الصفحة الواحدة (SPAs)، غالبًا ما تعاني من بطء في وقت تحميل الصفحة الأولي. يحدث هذا بسبب عدة أسباب:
- يحتاج المتصفح إلى انتظار تحميل وتشغيل كود React وحزمة التطبيق بالكامل قبل أن يتمكن الكود من إرسال طلبات لتحميل بعض البيانات.
- ينمو كود التطبيق مع كل ميزة جديدة وإضافة تبعية إضافية.
عدم وجود تقسيم تلقائي للكود
يمكن إدارة مشكلة بطء أوقات التحميل السابقة إلى حد ما باستخدام تقسيم الكود. ومع ذلك، إذا حاولت تقسيم الكود يدويًا، فغالبًا ما ستجعل الأداء أسوأ. من السهل إدخال شلالات شبكة عن غير قصد عند تقسيم الكود يدويًا. يوفر Next.js تقسيمًا تلقائيًا للكود مدمجًا في جهاز التوجيه الخاص به.
شلالات الشبكة
يحدث سبب شائع لضعف الأداء عندما تقوم التطبيقات بعمل طلبات متتابعة بين العميل والخادم لجلب البيانات. أحد الأنماط الشائعة لجلب البيانات في تطبيق SPA هو عرض عنصر نائب في البداية، ثم جلب البيانات بعد تحميل المكون. لسوء الحظ، هذا يعني أن المكون الفرعي الذي يجلب البيانات لا يمكنه البدء في الجلب حتى ينتهي المكون الأصلي من تحميل بياناته الخاصة.
بينما يتم دعم جلب البيانات على العميل باستخدام Next.js، فإنه يمنحك أيضًا خيار نقل جلب البيانات إلى الخادم، مما يمكنه القضاء على شلالات العميل-الخادم.
حالات تحميل سريعة ومقصودة
مع الدعم المدمج للبث عبر React Suspense، يمكنك أن تكون أكثر تحديدًا بشأن الأجزاء التي تريد تحميلها أولاً وبأي ترتيب دون إدخال شلالات شبكة.
هذا يتيح لك بناء صفحات يتم تحميلها بشكل أسرع والقضاء على تحولات التخطيط.
اختر استراتيجية جلب البيانات
اعتمادًا على احتياجاتك، يسمح لك Next.js باختيار استراتيجية جلب البيانات على أساس كل صفحة ومكون. يمكنك أن تقرر الجلب في وقت البناء، أو في وقت الطلب على الخادم، أو على العميل. على سبيل المثال، يمكنك جلب البيانات من نظام إدارة المحتوى الخاص بك وعرض مشاركات المدونة الخاصة بك في وقت البناء، والتي يمكن بعد ذلك تخزينها مؤقتًا بكفاءة على CDN.
Middleware
Next.js Middleware يسمح لك بتشغيل الكود على الخادم قبل اكتمال الطلب. هذا مفيد بشكل خاص لتجنب ظهور محتوى غير مصادق عليه عندما يزور المستخدم صفحة تتطلب مصادقة عن طريق توجيه المستخدم إلى صفحة تسجيل الدخول. يكون Middleware مفيدًا أيضًا للتجربة والتدويل.
تحسينات مدمجة
غالبًا ما يكون لـ الصور، والخطوط، ونصوص الطرف الثالث تأثير كبير على أداء التطبيق. يأتي Next.js مع مكونات مدمجة تقوم بتحسينها تلقائيًا لك.
خطوات النقل
هدفنا من هذا النقل هو الحصول على تطبيق Next.js يعمل في أسرع وقت ممكن، بحيث يمكنك بعد ذلك تبني ميزات Next.js تدريجيًا. في البداية، سنحافظ عليه كتطبيق يعمل فقط على جانب العميل (SPA) دون نقل جهاز التوجيه الحالي الخاص بك. هذا يساعد في تقليل فرص مواجهة مشكلات أثناء عملية النقل ويقلل من تعارضات الدمج.
الخطوة 1: تثبيت تبعية Next.js
أول شيء تحتاج إلى فعله هو تثبيت next
كتبعية:
npm install next@latest
الخطوة 2: إنشاء ملف تكوين Next.js
قم بإنشاء ملف next.config.mjs
في جذر مشروعك. سيحتوي هذا الملف على خيارات تكوين Next.js.
/** @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، يمكنك تخطي هذه الخطوة.
- أزل مرجع المشروع إلى
tsconfig.node.json
- أضف
./dist/types/**/*.ts
و./next-env.d.ts
إلى مصفوفةinclude
- أضف
./node_modules
إلى مصفوفةexclude
- أضف
{ "name": "next" }
إلى مصفوفةplugins
فيcompilerOptions
:"plugins": [{ "name": "next" }]
- اضبط
esModuleInterop
علىtrue
:"esModuleInterop": true
- اضبط
jsx
علىpreserve
:"jsx": "preserve"
- اضبط
allowJs
علىtrue
:"allowJs": true
- اضبط
forceConsistentCasingInFileNames
علىtrue
:"forceConsistentCasingInFileNames": true
- اضبط
incremental
علىtrue
:"incremental": true
إليك مثال على ملف 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
إلى ملف تخطيط جذر:
- قم بإنشاء دليل
app
جديد في دليلsrc
الخاص بك. - قم بإنشاء ملف
layout.tsx
جديد داخل دليلapp
:
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return null
}
export default function RootLayout({ children }) {
return null
}
جيد أن تعرف: يمكن استخدام امتدادات
.js
أو.jsx
أو.tsx
لملفات التخطيط.
- انسخ محتوى ملف
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>
)
}
- يتضمن 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>
)
}
- يتم إضافة أي ملفات بيانات وصفية مثل
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>
)
}
- أخيرًا، يمكن لـ 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 المدمج في الإطار (واجهة برمجة تطبيقات البيانات الوصفية). يتيح لك هذا النهج تحسين تحسين محركات البحث وقابلية مشاركة صفحاتك على الويب بسهولة أكبر.
الخطوة 5: إنشاء صفحة نقطة الدخول
في Next.js، يمكنك تحديد نقطة دخول لتطبيقك عن طريق إنشاء ملف page.tsx
. أقرب ما يعادله في Vite هو ملف main.tsx
. في هذه الخطوة، ستقوم بإعداد نقطة الدخول لتطبيقك.
- قم بإنشاء دليل
[[...slug]]
داخل دليلapp
الخاص بك.
نظرًا لأننا في هذا الدليل نهدف أولاً إلى إعداد Next.js كتطبيق صفحة واحدة (SPA)، تحتاج إلى أن تكون نقطة دخول الصفحة قادرة على التقاط جميع المسارات المحتملة لتطبيقك. لهذا، قم بإنشاء دليل جديد [[...slug]]
داخل دليل app
.
هذا الدليل هو ما يسمى
بقطاع مسار اختياري شامل.
يستخدم Next.js جهاز توجيه يعتمد على نظام الملفات حيث
تستخدم الأدلة لتحديد المسارات.
سيضمن هذا الدليل الخاص أن جميع مسارات تطبيقك سيتم توجيهها إلى ملف page.tsx
الموجود بداخله.
- قم بإنشاء ملف جديد
page.tsx
داخل دليلapp/[[...slug]]
بالمحتوى التالي:
import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // سنقوم بتحديث هذا لاحقًا
}
import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // سنقوم بتحديث هذا لاحقًا
}
معلومة مفيدة: يمكن استخدام امتدادات
.js
أو.jsx
أو.tsx
لملفات الصفحات.
هذا الملف هو مكون خادم (Server Component). عند تشغيل next build
، يتم تقديم الملف مسبقًا إلى أصل ثابت. لا يتطلب أي كود ديناميكي.
يستورد هذا الملف CSS العام الخاص بنا ويخبر generateStaticParams
أننا سننشئ مسارًا واحدًا فقط، وهو مسار الفهرس /
.
الآن، لننقل بقية تطبيق Vite الذي سيعمل على جانب العميل فقط.
'use client'
import React from 'react'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'
import React from 'react'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
هذا الملف هو مكون عميل (Client Component)، يتم تعريفه باستخدام التوجيه 'use client'
. لا يزال يتم تقديم مكونات العميل مسبقًا إلى HTML على الخادم قبل إرسالها إلى العميل.
نظرًا لأننا نريد بدء تطبيق يعمل على جانب العميل فقط، يمكننا تكوين Next.js لتعطيل التقديم المسبق من مكون App
فما دون.
const App = dynamic(() => import('../../App'), { ssr: false })
الآن، قم بتحديث صفحة نقطة الدخول لاستخدام المكون الجديد:
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
الخطوة 6: تحديث استيراد الصور الثابتة
يتعامل Next.js مع استيراد الصور الثابتة بشكل مختلف قليلاً عن Vite. مع Vite، يؤدي استيراد ملف صورة إلى إرجاع عنوان URL العام كسلسلة نصية:
import image from './img.png' // `image` ستكون '/assets/img.2d8efhg.png' في الإنتاج
export default function App() {
return <img src={image} />
}
مع Next.js، يُرجع استيراد الصور الثابتة كائنًا. يمكن بعد ذلك استخدام هذا الكائن مباشرة مع مكون Next.js <Image>
، أو يمكنك استخدام خاصية src
للكائن مع وسم <img>
الحالي.
يتمتع مكون <Image>
بمزايا إضافية مثل
تحسين الصور تلقائيًا. يضبط مكون <Image>
تلقائيًا سمات width
و height
لوسم <img>
الناتج بناءً على أبعاد الصورة. هذا يمنع حدوث تغييرات في التخطيط عند تحميل الصورة. ومع ذلك، يمكن أن يسبب هذا مشاكل إذا كان تطبيقك يحتوي على صور بأبعاد غير متساوية دون ضبط الآخر على auto
. عندما لا يتم ضبطه على auto
، سيعتمد البعد على قيمة سمة البعد لوسم <img>
، مما قد يجعل الصورة تظهر مشوهة.
سيؤدي الاحتفاظ بوسم <img>
إلى تقليل كمية التغييرات في تطبيقك ومنع المشاكل المذكورة أعلاه. يمكنك بعد ذلك الانتقال لاحقًا إلى مكون <Image>
للاستفادة من تحسين الصور عن طريق تكوين محمل (loader)، أو الانتقال إلى خادم Next.js الافتراضي الذي يحتوي على تحسين تلقائي للصور.
- قم بتحويل مسارات الاستيراد المطلقة للصور المستوردة من
/public
إلى استيراد نسبي:
// قبل
import logo from '/logo.png'
// بعد
import logo from '../public/logo.png'
- قم بتمرير خاصية
src
للصورة بدلاً من كائن الصورة بالكامل إلى وسم<img>
:
// قبل
<img src={logo} />
// بعد
<img src={logo.src} />
بدلاً من ذلك، يمكنك الرجوع إلى عنوان URL العام لأصل الصورة بناءً على اسم الملف. على سبيل المثال، سيتم تقديم الصورة public/logo.png
على /logo.png
لتطبيقك، والذي سيكون قيمة src
.
تحذير: إذا كنت تستخدم TypeScript، فقد تواجه أخطاء نوع عند الوصول إلى خاصية
src
. يمكنك تجاهلها بأمان الآن. سيتم إصلاحها بنهاية هذا الدليل.
الخطوة 7: ترحيل متغيرات البيئة
يدعم Next.js متغيرات البيئة .env
المشابهة لـ Vite.
الفرق الرئيسي هو البادئة المستخدمة لعرض متغيرات البيئة على جانب العميل.
- قم بتغيير جميع متغيرات البيئة ذات البادئة
VITE_
إلىNEXT_PUBLIC_
.
يعرض Vite بعض متغيرات البيئة المدمجة على كائن import.meta.env
الخاص الذي لا يدعمه Next.js. تحتاج إلى تحديث استخدامها كما يلي:
import.meta.env.MODE
⇒process.env.NODE_ENV
import.meta.env.PROD
⇒process.env.NODE_ENV === 'production'
import.meta.env.DEV
⇒process.env.NODE_ENV !== 'production'
import.meta.env.SSR
⇒typeof window !== 'undefined'
لا يوفر Next.js أيضًا متغير بيئة BASE_URL
مدمجًا. ومع ذلك، لا يزال بإمكانك تكوين واحد إذا كنت بحاجة إليه:
- أضف ما يلي إلى ملف
.env
الخاص بك:
# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
- قم بتعيين
basePath
إلىprocess.env.NEXT_PUBLIC_BASE_PATH
في ملف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
- قم بتحديث استخدامات
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
:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
# ...
.next
next-env.d.ts
dist
الآن قم بتشغيل npm run dev
، وافتح http://localhost:3000
. يجب أن ترى تطبيقك يعمل الآن على Next.js.
مثال: تحقق من طلب السحب هذا لمثال عمل لتطبيق Vite تم ترحيله إلى Next.js.
الخطوة 9: التنظيف
يمكنك الآن تنظيف قاعدة التعليمات البرمجية من آثار Vite ذات الصلة:
- احذف
main.tsx
- احذف
index.html
- احذف
vite-env.d.ts
- احذف
tsconfig.node.json
- احذف
vite.config.ts
- أزل تبعيات Vite
الخطوات التالية
إذا سار كل شيء وفقًا للخطة، فلديك الآن تطبيق Next.js يعمل كتطبيق صفحة واحدة. ومع ذلك، لم تستفد بعد من معظم مزايا Next.js، ولكن يمكنك الآن البدء في إجراء تغييرات تدريجية لجني جميع الفوائد. إليك ما قد ترغب في القيام به بعد ذلك:
- قم بالانتقال من React Router إلى موجه تطبيق Next.js للحصول على:
- تقسيم الشفرة تلقائيًا
- عرض الخادم المتدفق (Streaming Server-Rendering)
- مكونات خادم React (React Server Components)
- قم بتحسين الصور باستخدام مكون
<Image>
- قم بتحسين الخطوط باستخدام
next/font
- قم بتحسين البرامج النصية لجهات خارجية باستخدام مكون
<Script>
- قم بتحديث تكوين ESLint لدعم قواعد Next.js