如何升级到版本 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替代。useFormStatehook 在 React 19 中仍然可用,但已被弃用,并将在未来版本中移除。推荐使用useActionState,它包含额外的属性,例如直接读取pending状态。了解更多。useFormStatus现在包含额外的键,如data、method和action。如果你不使用 React 19,则只有pending键可用。了解更多。- 在 React 19 升级指南中阅读更多内容。
值得注意的是:如果你使用 TypeScript,请确保同时将
@types/react和@types/react-dom升级到最新版本。
异步请求 API(破坏性变更)
之前依赖运行时信息的同步动态 API 现在是异步的:
cookiesheadersdraftModelayout.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')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 UnsafeUnwrappedDraftModeparams 和 searchParams
异步 Layout
// 之前
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
}同步 Layout
// 之前
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
}异步 Page
// 之前
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
}同步 Page
'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
}
Route Handlers
// 之前
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
}runtime 配置(破坏性变更)
runtime 段配置以前除了 edge 之外还支持 experimental-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 选项,则将使用该选项。
// 由于这是根布局,应用程序中所有未设置自己的 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' }) // 未缓存
// ...
}Route Handlers
Route Handlers 中的 GET 函数默认不再被缓存。要将 GET 方法选择性加入缓存,你可以在 Route Handler 文件中使用路由配置选项,例如 export const dynamic = 'force-static'。
export const dynamic = 'force-static'
export async function GET() {}客户端路由器缓存
通过 <Link> 或 useRouter 在页面之间导航时,page 段不再从客户端路由器缓存中重用。但是,在浏览器的前进和后退导航以及共享布局期间,它们仍然会被重用。
要将页面段选择性加入缓存,你可以使用 staleTimes 配置选项:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfignext/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 = nextConfigserverExternalPackages
experimental.serverComponentsExternalPackages 现在稳定并重命名为 serverExternalPackages。
/** @type {import('next').NextConfig} */
const nextConfig = {
// 之前
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// 之后
serverExternalPackages: ['package-name'],
}
module.exports = nextConfigSpeed Insights
Speed Insights 的自动检测已在 Next.js 15 中移除。
要继续使用 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)
// ...
}