Как обновиться до версии 15
Обновление с 14 до 15
Для обновления до Next.js версии 15 вы можете использовать кодмод upgrade:
npx @next/codemod@canary upgrade latestЕсли вы предпочитаете делать это вручную, убедитесь, что устанавливаете последние версии Next.js и 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теперь 19. useFormStateбыл заменён наuseActionState. ХукuseFormStateвсё ещё доступен в React 19, но считается устаревшим и будет удалён в будущих версиях. Рекомендуется использоватьuseActionState, который включает дополнительные свойства, такие как прямое чтение состоянияpending. Подробнее.useFormStatusтеперь включает дополнительные ключи, такие какdata,methodиaction. Если вы не используете React 19, доступен только ключpending. Подробнее.- Читайте больше в руководстве по обновлению до React 19.
Полезно знать: Если вы используете TypeScript, убедитесь, что также обновили
@types/reactи@types/react-domдо их последних версий.
Асинхронные API запросов (Критическое изменение)
Ранее синхронные динамические API, зависящие от информации во время выполнения, теперь стали асинхронными:
cookiesheadersdraftModeparamsвlayout.js,page.js,route.js,default.js,opengraph-image,twitter-image,icon, иapple-icon.searchParamsвpage.js
Для упрощения миграции доступен кодмод, который автоматизирует процесс, и 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 UnsafeUnwrappedDraftModeimport { draftMode } from 'next/headers'
// До
const { isEnabled } = draftMode()
// После
// будет выводить предупреждение в режиме разработки
const { isEnabled } = draftMode()params и 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
}// До
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
}Синхронный 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
}// До
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. Чтобы исправить это, обновите конфигурацию runtime до 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 в макете или странице, вы можете использовать опцию конфигурации сегмента export const fetchCache = 'default-cache' сегмента маршрута. Если отдельные запросы fetch указывают опцию 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, вы можете использовать опцию конфигурации маршрута, такую как export const 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 = 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
Свойства geo и ip в NextRequest были удалены, так как эти значения теперь предоставляются вашим хостинг-провайдером. Для автоматизации миграции доступен кодмод.
Если вы используете Vercel, вы можете вместо этого использовать функции geolocation и ipAddress из @vercel/functions:
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)
// ...
}