كيفية الانتقال من Vite إلى Next.js
سيساعدك هذا الدليل في نقل تطبيق Vite موجود إلى Next.js.
لماذا التبديل؟
هناك عدة أسباب قد تدفعك للتبديل من Vite إلى Next.js:
بطء وقت تحميل الصفحة الأولي
إذا قمت ببناء تطبيقك باستخدام إضافة Vite الافتراضية لـ React، فإن تطبيقك هو تطبيق يعمل فقط على جانب العميل. غالبًا ما تعاني التطبيقات التي تعمل فقط على جانب العميل، والمعروفة أيضًا باسم تطبيقات الصفحة الواحدة (SPAs)، من بطء في وقت تحميل الصفحة الأولي. يحدث هذا لعدة أسباب:
- يحتاج المتصفح إلى انتظار تنزيل وتشغيل كود React وحزمة التطبيق بالكامل قبل أن يتمكن الكود من إرسال طلبات لتحميل بعض البيانات.
- ينمو كود التطبيق مع كل ميزة جديدة وإضافة تبعية إضافية.
عدم وجود تقسيم تلقائي للكود
يمكن إدارة مشكلة بطء أوقات التحميل السابقة إلى حد ما باستخدام تقسيم الكود. ومع ذلك، إذا حاولت تقسيم الكود يدويًا، فغالبًا ما ستجعل الأداء أسوأ. من السهل إدخال شلالات شبكة عن غير قصد عند تقسيم الكود يدويًا. يوفر Next.js تقسيمًا تلقائيًا للكود مدمجًا في جهاز التوجيه الخاص به.
شلالات الشبكة
أحد الأسباب الشائعة لضعف الأداء يحدث عندما تقوم التطبيقات بعمل طلبات متتابعة بين العميل والخادم لجلب البيانات. أحد الأنماط الشائعة لجلب البيانات في تطبيق SPA هو عرض عنصر نائب مبدئيًا، ثم جلب البيانات بعد تحميل المكون. لسوء الحظ، هذا يعني أن المكون الفرعي الذي يجلب البيانات لا يمكنه البدء في الجلب حتى ينتهي المكون الأصلي من تحميل بياناته الخاصة.
بينما يتم دعم جلب البيانات على العميل باستخدام Next.js، فإنه يمنحك أيضًا خيار نقل جلب البيانات إلى الخادم، مما يمكنه القضاء على شلالات العميل-الخادم.
حالات تحميل سريعة ومقصودة
مع دعم مدمج للتدفق عبر React Suspense، يمكنك أن تكون أكثر تحديدًا بشأن أجزاء واجهة المستخدم التي تريد تحميلها أولًا وبأي ترتيب دون إدخال شلالات شبكة.
هذا يتيح لك بناء صفحات يتم تحميلها بشكل أسرع والقضاء على تحولات التخطيط.
اختر استراتيجية جلب البيانات
حسب احتياجاتك، يسمح لك Next.js باختيار استراتيجية جلب البيانات على أساس كل صفحة ومكون. يمكنك أن تقرر الجلب في وقت البناء، في وقت الطلب على الخادم، أو على العميل. على سبيل المثال، يمكنك جلب البيانات من نظام إدارة المحتوى الخاص بك وعرض مشاركات مدونتك في وقت البناء، والتي يمكن بعد ذلك تخزينها بكفاءة على CDN.
البرمجيات الوسيطة
برمجيات Next.js الوسيطة تتيح لك تشغيل الكود على الخادم قبل اكتمال الطلب. هذا مفيد بشكل خاص لتجنب ظهور محتوى غير مصادق عليه عندما يزور المستخدم صفحة تتطلب مصادقة عن طريق توجيه المستخدم إلى صفحة تسجيل الدخول. كما أن البرمجيات الوسيطة مفيدة أيضًا للتجارب والدولية.
تحسينات مدمجة
غالبًا ما يكون للصور والخطوط والنصوص البرمجية لجهات خارجية تأثير كبير على أداء التطبيق. يأتي 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 '...'
}
export default function RootLayout({ children }) {
return '...'
}
جيد أن تعرف: يمكن استخدام امتدادات
.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 للحصول على:
- قم بتحسين الصور باستخدام مكون
<Image>
- قم بتحسين الخطوط باستخدام
next/font
- قم بتحسين البرامج النصية لجهات خارجية باستخدام مكون
<Script>
- قم بتحديث تكوين ESLint لدعم قواعد Next.js