كيفية الترقية إلى الإصدار 15
الترقية من الإصدار 14 إلى 15
للتحديث إلى إصدار Next.js 15، يمكنك استخدام أداة upgrade
التلقائية:
npx @next/codemod@canary upgrade latest
إذا كنت تفضل القيام بذلك يدويًا، تأكد من تثبيت أحدث إصدارات Next و React:
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
معلومة مفيدة:
- إذا رأيت تحذيرًا حول تبعيات الأقران، قد تحتاج إلى تحديث
react
وreact-dom
إلى الإصدارات المقترحة، أو يمكنك استخدام علم--force
أو--legacy-peer-deps
لتجاهل التحذير. لن يكون هذا ضروريًا بمجرد استقرار كل من Next.js 15 و React 19.
React 19
- الحد الأدنى لإصدارات
react
وreact-dom
أصبح الآن 19. - تم استبدال
useFormState
بـuseActionState
. لا يزال خطافuseFormState
متاحًا في React 19، ولكنه مهمل وسيتم إزالته في إصدار مستقبلي. يُوصى باستخدامuseActionState
لأنه يتضمن خصائص إضافية مثل قراءة حالةpending
مباشرة. معرفة المزيد. useFormStatus
يتضمن الآن مفاتيح إضافية مثلdata
وmethod
وaction
. إذا كنت لا تستخدم React 19، فإن مفتاحpending
فقط هو المتاح. معرفة المزيد.- اقرأ المزيد في دليل ترقية React 19.
معلومة مفيدة: إذا كنت تستخدم TypeScript، تأكد من تحديث
@types/react
و@types/react-dom
إلى أحدث إصداراتهم.
واجهات برمجة التطبيقات غير المتزامنة (تغيير كاسر)
أصبحت واجهات برمجة التطبيقات الديناميكية التي كانت متزامنة وتعتمد على معلومات وقت التشغيل الآن غير متزامنة:
cookies
headers
draftMode
params
فيlayout.js
،page.js
،route.js
،default.js
،opengraph-image
،twitter-image
،icon
، وapple-icon
.searchParams
فيpage.js
لتسهيل عملية الترحيل، تتوفر أداة تلقائية لأتمتة العملية ويمكن الوصول إلى واجهات برمجة التطبيقات مؤقتًا بشكل متزامن.
cookies
الاستخدام الموصى به غير المتزامن
import { cookies } from 'next/headers'
// قبل
const cookieStore = cookies()
const token = cookieStore.get('token')
// بعد
const cookieStore = await cookies()
const token = cookieStore.get('token')
الاستخدام المتزامن المؤقت
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
// قبل
const cookieStore = cookies()
const token = cookieStore.get('token')
// بعد
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// سيسجل تحذيرًا في وضع التطوير
const token = cookieStore.get('token')
import { cookies } from 'next/headers'
// قبل
const cookieStore = cookies()
const token = cookieStore.get('token')
// بعد
const cookieStore = cookies()
// سيسجل تحذيرًا في وضع التطوير
const token = cookieStore.get('token')
headers
الاستخدام الموصى به غير المتزامن
import { headers } from 'next/headers'
// قبل
const headersList = headers()
const userAgent = headersList.get('user-agent')
// بعد
const headersList = await headers()
const userAgent = headersList.get('user-agent')
الاستخدام المتزامن المؤقت
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
// قبل
const headersList = headers()
const userAgent = headersList.get('user-agent')
// بعد
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// سيسجل تحذيرًا في وضع التطوير
const userAgent = headersList.get('user-agent')
import { headers } from 'next/headers'
// قبل
const headersList = headers()
const userAgent = headersList.get('user-agent')
// بعد
const headersList = headers()
// سيسجل تحذيرًا في وضع التطوير
const userAgent = headersList.get('user-agent')
draftMode
الاستخدام الموصى به غير المتزامن
import { draftMode } from 'next/headers'
// قبل
const { isEnabled } = draftMode()
// بعد
const { isEnabled } = await draftMode()
الاستخدام المتزامن المؤقت
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// قبل
const { isEnabled } = draftMode()
// بعد
// سيسجل تحذيرًا في وضع التطوير
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
import { draftMode } from 'next/headers'
// قبل
const { isEnabled } = draftMode()
// بعد
// سيسجل تحذيرًا في وضع التطوير
const { isEnabled } = draftMode()
params
و searchParams
التخطيط غير المتزامن
// قبل
type Params = { slug: string }
export function generateMetadata({ params }: { params: Params }) {
const { slug } = params
}
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// بعد
type Params = Promise<{ slug: string }>
export async function generateMetadata({ params }: { params: Params }) {
const { slug } = await params
}
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = await params
}
// قبل
export function generateMetadata({ params }) {
const { slug } = params
}
export default async function Layout({ children, params }) {
const { slug } = params
}
// بعد
export async function generateMetadata({ params }) {
const { slug } = await params
}
export default async function Layout({ children, params }) {
const { slug } = await params
}
التخطيط المتزامن
// قبل
type Params = { slug: string }
export default function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// بعد
import { use } from 'react'
type Params = Promise<{ slug: string }>
export default function Layout(props: {
children: React.ReactNode
params: Params
}) {
const params = use(props.params)
const slug = params.slug
}
// قبل
export default function Layout({ children, params }) {
const { slug } = params
}
// بعد
import { use } from 'react'
export default async function Layout(props) {
const params = use(props.params)
const slug = params.slug
}
الصفحة غير المتزامنة
// قبل
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
export function generateMetadata({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
export default async function Page({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
// بعد
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
export async function generateMetadata(props: {
params: Params
searchParams: SearchParams
}) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
export default async function Page(props: {
params: Params
searchParams: SearchParams
}) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
// قبل
export function generateMetadata({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// بعد
export async function generateMetadata(props) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
export async function Page(props) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
الصفحة المتزامنة
'use client'
// قبل
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
export default function Page({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
// بعد
import { use } from 'react'
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
export default function Page(props: {
params: Params
searchParams: SearchParams
}) {
const params = use(props.params)
const searchParams = use(props.searchParams)
const slug = params.slug
const query = searchParams.query
}
// قبل
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// بعد
import { use } from "react"
export default function Page(props) {
const params = use(props.params)
const searchParams = use(props.searchParams)
const slug = params.slug
const query = searchParams.query
}
معالجات المسار
// قبل
type Params = { slug: string }
export async function GET(request: Request, segmentData: { params: Params }) {
const params = segmentData.params
const slug = params.slug
}
// بعد
type Params = Promise<{ slug: string }>
export async function GET(request: Request, segmentData: { params: Params }) {
const params = await segmentData.params
const slug = params.slug
}
// قبل
export async function GET(request, segmentData) {
const params = segmentData.params
const slug = params.slug
}
// بعد
export async function GET(request, segmentData) {
const params = await segmentData.params
const slug = params.slug
}
تكوين runtime
(تغيير كاسر)
كان تكوين runtime
لقطاع المسار يدعم سابقًا قيمة experimental-edge
بالإضافة إلى edge
. يشير كلا التكوينين إلى نفس الشيء، ولتبسيط الخيارات، سنقوم الآن بإرجاع خطأ إذا تم استخدام experimental-edge
. لإصلاح هذا، قم بتحديث تكوين runtime
إلى edge
. تتوفر أداة تلقائية للقيام بذلك تلقائيًا.
طلبات fetch
لم تعد طلبات fetch
مخزنة مؤقتًا افتراضيًا.
لاختيار تخزين طلبات fetch
معينة مؤقتًا، يمكنك تمرير خيار cache: 'force-cache'
.
export default async function RootLayout() {
const a = await fetch('https://...') // غير مخزن مؤقتًا
const b = await fetch('https://...', { cache: 'force-cache' }) // مخزن مؤقتًا
// ...
}
لاختيار تخزين جميع طلبات fetch
في تخطيط أو صفحة مؤقتًا، يمكنك استخدام خيار تكوين القطاع export const fetchCache = 'default-cache'
خيار تكوين القطاع. إذا حددت طلبات fetch
الفردية خيار cache
، فسيتم استخدام ذلك بدلاً من ذلك.
// نظرًا لأن هذا هو التخطيط الجذري، سيتم تخزين جميع طلبات fetch في التطبيق
// التي لا تحدد خيار التخزين المؤقت الخاص بها.
export const fetchCache = 'default-cache'
export default async function RootLayout() {
const a = await fetch('https://...') // مخزن مؤقتًا
const b = await fetch('https://...', { cache: 'no-store' }) // غير مخزن مؤقتًا
// ...
}
معالجات المسار
لم تعد دوال GET
في معالجات المسار مخزنة مؤقتًا افتراضيًا. لاختيار تخزين طرق GET
مؤقتًا، يمكنك استخدام خيار تكوين المسار مثل export const dynamic = 'force-static'
في ملف معالج المسار.
export const dynamic = 'force-static'
export async function GET() {}
ذاكرة التوجيه من جانب العميل
عند التنقل بين الصفحات عبر <Link>
أو useRouter
، لم تعد أجزاء الصفحة معاد استخدامها من ذاكرة التوجيه من جانب العميل. ومع ذلك، لا تزال معاد استخدامها أثناء التنقل للخلف وللأمام في المتصفح وللتخطيطات المشتركة.
لاختيار تخزين أجزاء الصفحة مؤقتًا، يمكنك استخدام خيار التكوين staleTimes
:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfig
لا تزال التخطيطات وحالات التحميل مخزنة مؤقتًا ومعاد استخدامها أثناء التنقل.
next/font
تمت إزالة الحزمة @next/font
لصالح next/font
المدمج. تتوفر أداة تلقائية لإعادة تسمية الواردات الخاصة بك بأمان وتلقائيًا.
// قبل
import { Inter } from '@next/font/google'
// بعد
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
أصبح experimental.bundlePagesExternals
مستقرًا وتمت إعادة تسميته إلى bundlePagesRouterDependencies
.
/** @type {import('next').NextConfig} */
const nextConfig = {
// قبل
experimental: {
bundlePagesExternals: true,
},
// بعد
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
أصبح experimental.serverComponentsExternalPackages
مستقرًا وتمت إعادة تسميته إلى serverExternalPackages
.
/** @type {import('next').NextConfig} */
const nextConfig = {
// قبل
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// بعد
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
Speed Insights
تمت إزالة الأدوات التلقائية لـ Speed Insights في Next.js 15.
لاستمرار استخدام Speed Insights، اتبع دليل البدء السريع لـ Vercel Speed Insights.
تحديد الموقع الجغرافي في NextRequest
تمت إزالة خصائص geo
و ip
من NextRequest
حيث يتم توفير هذه القيم من قبل مزود الاستضافة الخاص بك. يتوفر أداة تحويل الشفرة (codemod) لأتمتة عملية الترحيل هذه.
إذا كنت تستخدم Vercel، يمكنك بدلاً من ذلك استخدام دوال geolocation
و ipAddress
من @vercel/functions
:
import { geolocation } from '@vercel/functions'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { city } = geolocation(request)
// ...
}
import { ipAddress } from '@vercel/functions'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const ip = ipAddress(request)
// ...
}