版本 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
Hook 在 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'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = await cookies()
const token = cookieStore.get('token')
暫時的同步用法
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// will log a warning in dev
const token = cookieStore.get('token')
headers
建議的非同步用法
import { headers } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = await headers()
const userAgent = headersList.get('user-agent')
暫時的同步用法
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// will log a warning in dev
const userAgent = headersList.get('user-agent')
draftMode
建議的非同步用法
import { draftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
const { isEnabled } = await draftMode()
暫時的同步用法
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
// will log a warning in dev
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
params
& searchParams
非同步版面配置
// Before
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
}
// After
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
}
同步版面配置
// Before
type Params = { slug: string }
export default function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// After
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
}
非同步頁面
// Before
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
}
// After
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
}
同步頁面
'use client'
// Before
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
}
// After
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
}
// Before
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// After
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
}
路由處理器
// Before
type Params = { slug: string }
export async function GET(request: Request, segmentData: { params: Params }) {
const params = segmentData.params
const slug = params.slug
}
// After
type Params = Promise<{ slug: string }>
export async function GET(request: Request, segmentData: { params: Params }) {
const params = await segmentData.params
const slug = params.slug
}
// Before
export async function GET(request, segmentData) {
const params = segmentData.params
const slug = params.slug
}
// After
export async function GET(request, segmentData) {
const params = await segmentData.params
const slug = params.slug
}
runtime
設定 (重大變更)
除了 edge
之外,runtime
區段設定先前也支援 experimental-edge
值。這兩個設定都指向同一件事,為了簡化選項,如果使用 experimental-edge
,現在會產生錯誤。若要修正此問題,請將你的 runtime
設定更新為 edge
。codemod 可自動執行此操作。
fetch
請求
fetch
請求不再預設快取。
若要選擇將特定的 fetch
請求加入快取,你可以傳遞 cache: 'force-cache'
選項。
export default async function RootLayout() {
const a = await fetch('https://...') // Not Cached
const b = await fetch('https://...', { cache: 'force-cache' }) // Cached
// ...
}
若要選擇將版面配置或頁面中的所有 fetch
請求加入快取,你可以使用 export const fetchCache = 'default-cache'
區段設定選項。如果個別的 fetch
請求指定了 cache
選項,則將改為使用該選項。
// Since this is the root layout, all fetch requests in the app
// that don't set their own cache option will be cached.
export const fetchCache = 'default-cache'
export default async function RootLayout() {
const a = await fetch('https://...') // Cached
const b = await fetch('https://...', { cache: 'no-store' }) // Not cached
// ...
}
路由處理器
路由處理器中的 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 可用來安全且自動地重新命名你的匯入。
// Before
import { Inter } from '@next/font/google'
// After
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
experimental.bundlePagesExternals
現在已穩定,並重新命名為 bundlePagesRouterDependencies
。
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
bundlePagesExternals: true,
},
// After
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
experimental.serverComponentsExternalPackages
現在已穩定,並重新命名為 serverExternalPackages
。
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// After
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
Speed Insights
Next.js 15 中移除了 Speed Insights 的自動檢測功能。
若要繼續使用 Speed Insights,請依照 Vercel Speed Insights 快速入門 指南操作。
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)
// ...
}
這有幫助嗎?