版本 15
从 14 升级到 15
¥Upgrading from 14 to 15
要更新到 Next.js 版本 15,你可以使用 upgrade
codemod:
¥To update to Next.js version 15, you can use the upgrade
codemod:
npx @next/codemod@canary upgrade latest
如果你更喜欢手动执行此操作,请确保你正在安装最新的 Next 和 React RC,例如:
¥If you prefer to do it manually, ensure that you're installing the latest Next & React RC, e.g.:
npm i next@latest react@rc react-dom@rc eslint-config-next@latest
很高兴知道:
¥Good to know:
如果你看到对等依赖警告,则可能需要将
react
和react-dom
更新为建议的版本,或者使用--force
或--legacy-peer-deps
标志忽略警告。一旦 Next.js 15 和 React 19 都稳定下来,这将不再必要。¥If you see a peer dependencies warning, you may need to update
react
andreact-dom
to the suggested versions, or you use the--force
or--legacy-peer-deps
flag to ignore the warning. This won't be necessary once both Next.js 15 and React 19 are stable.如果你使用 TypeScript,则需要暂时覆盖 React 类型。请参阅 React 19 RC 升级指南 了解更多信息。
¥If you are using TypeScript, you'll need to temporarily override the React types. See the React 19 RC upgrade guide for more information.
React 19
-
react
和react-dom
的最低版本现在为 19。¥The minimum versions of
react
andreact-dom
is now 19. -
useFormState
已被useActionState
取代。useFormState
钩子在 React 19 中仍然可用,但已被弃用,并将在未来版本中删除。建议使用useActionState
,它包含其他属性,例如直接读取pending
状态。了解更多。¥
useFormState
has been replaced byuseActionState
. TheuseFormState
hook is still available in React 19, but it is deprecated and will be removed in a future release.useActionState
is recommended and includes additional properties like reading thepending
state directly. Learn more. -
useFormStatus
现在包含其他密钥,如data
、method
和action
。如果你没有使用 React 19,则只有pending
键可用。了解更多。¥
useFormStatus
now includes additional keys likedata
,method
, andaction
. If you are not using React 19, only thepending
key is available. Learn more. -
在 React 19 升级指南 中阅读更多内容。
¥Read more in the React 19 upgrade guide.
异步请求 API(重大更改)
¥Async Request APIs (Breaking change)
以前依赖于运行时信息的同步动态 API 现在是异步的:
¥Previously synchronous Dynamic APIs that rely on runtime information are now asynchronous:
-
layout.js
、page.js
、route.js
、default.js
、opengraph-image
、twitter-image
、icon
和apple-icon
中的params
。¥
params
inlayout.js
,page.js
,route.js
,default.js
,opengraph-image
,twitter-image
,icon
, andapple-icon
. -
searchParams
于page.js
¥
searchParams
inpage.js
为了减轻迁移负担,可以使用 代码模式可用 来自动化该过程,并且可以暂时同步访问 API。
¥To ease the burden of migration, a codemod is available to automate the process and the APIs can temporarily be accessed synchronously.
cookies
建议的异步用法
¥Recommended Async Usage
import { cookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = await cookies()
const token = cookieStore.get('token')
临时同步使用
¥Temporary Synchronous Usage
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')
import { cookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = cookies()
// will log a warning in dev
const token = cookieStore.get('token')
headers
建议的异步用法
¥Recommended Async Usage
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')
临时同步使用
¥Temporary Synchronous Usage
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')
import { headers } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = headers()
// will log a warning in dev
const userAgent = headersList.get('user-agent')
draftMode
建议的异步用法
¥Recommended Async Usage
import { draftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
const { isEnabled } = await draftMode()
临时同步使用
¥Temporary Synchronous Usage
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
import { draftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
// will log a warning in dev
const { isEnabled } = draftMode()
params
& searchParams
异步布局
¥Asynchronous Layout
// 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
export function generateMetadata({ params }) {
const { slug } = params
}
export default async function Layout({ children, params }) {
const { slug } = params
}
// After
export async function generateMetadata({ params }) {
const { slug } = await params
}
export default async function Layout({ children, params }) {
const { slug } = await params
}
同步布局
¥Synchronous Layout
// 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
export default function Layout({ children, params }) {
const { slug } = params
}
// After
import { use } from 'react'
export default async function Layout(props) {
const params = use(props.params)
const slug = params.slug
}
异步页面
¥Asynchronous Page
// 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
}
// Before
export function generateMetadata({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// After
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
}
同步页面
¥Synchronous Page
'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
}
路由处理程序
¥Route Handlers
// 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
配置(重大更改)
¥runtime
configuration (Breaking change)
runtime
分段配置 以前除了 edge
之外还支持 experimental-edge
值。两种配置都引用同一件事,为了简化选项,如果使用 experimental-edge
,我们现在会出错。要解决此问题,请将 runtime
配置更新为 edge
。codemod 可用于自动执行此操作。
¥The runtime
segment configuration previously supported a value of experimental-edge
in addition to edge
. Both configurations refer to the same thing, and to simplify the options, we will now error if experimental-edge
is used. To fix this, update your runtime
configuration to edge
. A codemod is available to automatically do this.
fetch
请求
¥fetch
requests
fetch
请求 不再默认缓存。
¥fetch
requests are no longer cached by default.
要将特定的 fetch
请求选择为缓存,你可以传递 cache: 'force-cache'
选项。
¥To opt specific fetch
requests into caching, you can pass the cache: 'force-cache'
option.
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
选项,则将改用该选项。
¥To opt all fetch
requests in a layout or page into caching, you can use the export const fetchCache = 'default-cache'
segment config option. If individual fetch
requests specify a cache
option, that will be used instead.
// 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
// ...
}
路由处理程序
¥Route Handlers
路由处理程序 中的 GET
功能不再默认缓存。要将 GET
方法选择为缓存,你可以在路由处理程序文件中使用 路由配置选项(例如 export const dynamic = 'force-static'
)。
¥GET
functions in Route Handlers are no longer cached by default. To opt GET
methods into caching, you can use a route config option such as export const dynamic = 'force-static'
in your Route Handler file.
export const dynamic = 'force-static'
export async function GET() {}
客户端路由缓存
¥Client-side Router Cache
通过 <Link>
或 useRouter
在页面之间导航时,page 段不再从客户端路由缓存中重用。但是,它们在浏览器前后导航期间以及共享布局中仍会被重用。
¥When navigating between pages via <Link>
or useRouter
, page segments are no longer reused from the client-side router cache. However, they are still reused during browser backward and forward navigation and for shared layouts.
要将页面段选择为缓存,你可以使用 staleTimes
配置选项:
¥To opt page segments into caching, you can use the staleTimes
config option:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfig
¥Layouts and loading states are still cached and reused on navigation.
next/font
@next/font
包已被删除,取而代之的是内置的 next/font
。代码模式可用 可以安全、自动地重命名你的导入。
¥The @next/font
package has been removed in favor of the built-in next/font
. A codemod is available to safely and automatically rename your imports.
// Before
import { Inter } from '@next/font/google'
// After
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
experimental.bundlePagesExternals
现已稳定并重命名为 bundlePagesRouterDependencies
。
¥experimental.bundlePagesExternals
is now stable and renamed to bundlePagesRouterDependencies
.
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
bundlePagesExternals: true,
},
// After
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
experimental.serverComponentsExternalPackages
现已稳定并重命名为 serverExternalPackages
。
¥experimental.serverComponentsExternalPackages
is now stable and renamed to 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 的自动检测。
¥Auto instrumentation for Speed Insights was removed in Next.js 15.
要继续使用 Speed Insights,请遵循 Vercel Speed Insights 快速入门 指南。
¥To continue using Speed Insights, follow the Vercel Speed Insights Quickstart guide.
NextRequest
地理位置
¥NextRequest
Geolocation
NextRequest
上的 geo
和 ip
属性已被删除,因为这些值由你的托管服务提供商提供。codemod 可用于自动执行此迁移。
¥The geo
and ip
properties on NextRequest
have been removed as these values are provided by your hosting provider. A codemod is available to automate this migration.
如果你使用的是 Vercel,则可以选择使用 [@vercel/functions](https://vercel.com/docs/functions/vercel-functions-package) 中的
geolocation和
ipAddress` 函数:
¥If you are using Vercel, you can alternatively use the geolocation
and ipAddress
functions from `@vercel/functions instead:
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)
// ...
}