如何升级到版本 15
从 14 升级到 15
要更新到 Next.js 版本 15,你可以使用 upgrade
codemod:
Terminal
npx @next/codemod@canary upgrade latest
如果你更喜欢手动操作,确保你正在安装最新的 Next & React 版本:
Terminal
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
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'
// 之前
const cookieStore = cookies()
const token = cookieStore.get('token')
// 之后
const cookieStore = await cookies()
const token = cookieStore.get('token')
临时同步用法
- TypeScript
- JavaScript
app/page.tsx
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')
app/page.js
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')
临时同步用法
- TypeScript
- JavaScript
app/page.tsx
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
// 之前
const headersList = headers()
const userAgent = headersList.get('user-agent')
// 之后
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// will log a warning in dev
const userAgent = headersList.get('user-agent')
app/page.js
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()
临时同步用法
- TypeScript
- JavaScript
app/page.tsx
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// 之前
const { isEnabled } = draftMode()
// 之后
// will log a warning in dev
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
app/page.js
import { draftMode } from 'next/headers'
// 之前
const { isEnabled } = draftMode()
// 之后
// 在开发中会记录警告
const { isEnabled } = draftMode()
params
& searchParams
异步布局
- TypeScript
- JavaScript
app/layout.tsx
// 之前
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
}
app/layout.js
// 之前
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
}
同步布局
- TypeScript
- JavaScript
app/layout.tsx
// 之前
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
}
app/layout.js
// 之前
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
}
异步页面
- TypeScript
- JavaScript
app/page.tsx
// 之前
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
}
app/page.js
// 之前
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
}
路由处理器
- TypeScript
- JavaScript
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
}