كيفية الترقية إلى الإصدار 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إلى أحدث إصداراتهم.
واجهات برمجة التطبيقات غير المتزامنة (تغيير كاسر)
أصبحت واجهات برمجة التطبيقات الديناميكية التي كانت متزامنة وتعتمد على معلومات وقت التشغيل الآن غير متزامنة:
cookiesheadersdraftModeparamsفي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 UnsafeUnwrappedDraftModeimport { 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 = nextConfigserverExternalPackages
أصبح experimental.serverComponentsExternalPackages مستقرًا وتمت إعادة تسميته إلى serverExternalPackages.
/** @type {import('next').NextConfig} */
const nextConfig = {
// قبل
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// بعد
serverExternalPackages: ['package-name'],
}
module.exports = nextConfigSpeed 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)
// ...
}