如何升級至版本 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 警告,可能需要將 reactreact-dom 更新至建議版本,或使用 --force--legacy-peer-deps 標記忽略警告。當 Next.js 15 和 React 19 都穩定後,此步驟將不再需要。

React 19

  • reactreact-dom 的最低版本現在為 19。
  • useFormState 已被 useActionState 取代。useFormState 鉤子在 React 19 中仍然可用,但已被棄用並將在未來版本中移除。建議使用 useActionState,它包含如直接讀取 pending 狀態等額外屬性。了解更多
  • useFormStatus 現在包含如 datamethodaction 等額外鍵值。如果您未使用 React 19,則僅有 pending 鍵值可用。了解更多
  • 詳情請參閱 React 19 升級指南

須知事項: 如果您使用 TypeScript,請確保同時升級 @types/react@types/react-dom 至最新版本。

非同步請求 API (重大變更)

先前依賴運行時資訊的同步動態 API 現在改為非同步

為減輕遷移負擔,我們提供了 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')

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')

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

paramssearchParams

非同步版面配置

// 之前
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
}

同步版面配置

// 之前
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
}

非同步頁面

// 之前
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
}

同步頁面

'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
}

路由處理器

app/api/route.ts
// 之前
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
}
app/api/route.js
// 之前
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-edgeedge 值。這兩種配置指的是同一件事,為簡化選項,現在如果使用 experimental-edge 將會報錯。要解決此問題,請將您的 runtime 配置更新為 edge。我們提供了 codemod 來自動執行此操作。

fetch 請求

fetch 請求 預設不再快取。

若要為特定 fetch 請求啟用快取,可以傳遞 cache: 'force-cache' 選項。

app/layout.js
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 選項,則會優先使用該選項。

app/layout.js
// 由於這是根版面配置,應用中所有未設定自身快取選項的 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'

app/api/route.js
export const dynamic = 'force-static'

export async function GET() {}

客戶端路由快取

透過 <Link>useRouter 在頁面之間導航時,頁面 區段不再從客戶端路由快取中重複使用。然而,在瀏覽器前進和後退導航以及共享版面配置時仍會重複使用。

若要為頁面區段啟用快取,可以使用 staleTimes 配置選項:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
      static: 180,
    },
  },
}

module.exports = nextConfig

版面配置載入狀態 仍會在導航時被快取並重複使用。

next/font

@next/font 套件已被移除,改用內建的 next/font。我們提供了 codemod 來安全且自動地重新命名您的導入。

app/layout.js
// 之前
import { Inter } from '@next/font/google'

// 之後
import { Inter } from 'next/font/google'

bundlePagesRouterDependencies

experimental.bundlePagesExternals 現已穩定並更名為 bundlePagesRouterDependencies

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 之前
  experimental: {
    bundlePagesExternals: true,
  },

  // 之後
  bundlePagesRouterDependencies: true,
}

module.exports = nextConfig

serverExternalPackages

experimental.serverComponentsExternalPackages 現已穩定並更名為 serverExternalPackages

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 之前
  experimental: {
    serverComponentsExternalPackages: ['package-name'],
  },

  // 之後
  serverExternalPackages: ['package-name'],
}

module.exports = nextConfig

速度洞察

Next.js 15 中移除了速度洞察的自動檢測功能。

若要繼續使用速度洞察,請遵循 Vercel 速度洞察快速入門 指南。

NextRequest 地理位置功能

NextRequest 上的 geoip 屬性已被移除,因為這些值現在由您的託管服務提供商提供。我們提供了一個 codemod 工具 來自動化此遷移過程。

如果您使用 Vercel,可以改用 @vercel/functions 中的 geolocationipAddress 函數:

middleware.ts
import { geolocation } from '@vercel/functions'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { city } = geolocation(request)

  // ...
}
middleware.ts
import { ipAddress } from '@vercel/functions'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const ip = ipAddress(request)

  // ...
}