如何升级至版本 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
的最低版本要求现为 19useFormState
已被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
为降低迁移成本,我们提供了代码修改工具来自动化此过程,且这些 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
将报错。请将配置更新为 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
请求启用缓存,可使用 fetchCache = 'default-cache'
路由段配置选项。若单个 fetch
请求指定了 cache
选项,则以该选项为准。
// 作为根布局,应用中所有未设置 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
方法启用缓存,可在路由处理器文件中使用 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
速度分析
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)
// ...
}