如何升級至版本 15
從 14 升級至 15
要更新至 Next.js 版本 15,您可以使用 upgrade
codemod:
npx @next/codemod@canary upgrade latest
如果您偏好手動升級,請確保安裝最新的 Next 和 React 版本:
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
須知事項:
- 如果您看到 peer dependencies 警告,可能需要將
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
至最新版本。
非同步請求 API (重大變更)
先前依賴運行時資訊的同步動態 API 現在改為非同步:
cookies
headers
draftMode
layout.js
、page.js
、route.js
、default.js
、opengraph-image
、twitter-image
、icon
和apple-icon
中的params
page.js
中的searchParams
為減輕遷移負擔,我們提供了 codemod 來自動化此過程,且這些 API 可暫時以同步方式存取。
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
。我們提供了 codemod 來自動執行此操作。
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
。我們提供了 codemod 來安全且自動地重新命名您的導入。
// 之前
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
速度洞察
Next.js 15 中移除了速度洞察的自動檢測功能。
若要繼續使用速度洞察,請遵循 Vercel 速度洞察快速入門 指南。
NextRequest
地理位置功能
NextRequest
上的 geo
和 ip
屬性已被移除,因為這些值現在由您的託管服務提供商提供。我們提供了一個 codemod 工具 來自動化此遷移過程。
如果您使用 Vercel,可以改用 @vercel/functions
中的 geolocation
和 ipAddress
函數:
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)
// ...
}