Upgrade auf Version 15
Upgrade von Version 14 auf 15
Um auf Next.js Version 15 zu aktualisieren, können Sie den upgrade
-Codemod verwenden:
npx @next/codemod@canary upgrade latest
Wenn Sie es manuell durchführen möchten, stellen Sie sicher, dass Sie die neuesten Versionen von Next und React installieren:
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
Wissenswert:
- Falls Sie eine Warnung zu Peer-Abhängigkeiten sehen, müssen Sie möglicherweise
react
undreact-dom
auf die vorgeschlagenen Versionen aktualisieren oder das Flag--force
oder--legacy-peer-deps
verwenden, um die Warnung zu ignorieren. Dies wird nicht mehr notwendig sein, sobald sowohl Next.js 15 als auch React 19 stabil sind.
React 19
- Die Mindestversionen von
react
undreact-dom
sind nun 19. useFormState
wurde durchuseActionState
ersetzt. DeruseFormState
-Hook ist in React 19 noch verfügbar, jedoch als veraltet markiert und wird in einer zukünftigen Version entfernt.useActionState
wird empfohlen und enthält zusätzliche Eigenschaften wie das direkte Auslesen despending
-Status. Mehr erfahren.useFormStatus
enthält nun zusätzliche Schlüssel wiedata
,method
undaction
. Wenn Sie nicht React 19 verwenden, ist nur derpending
-Schlüssel verfügbar. Mehr erfahren.- Weitere Informationen finden Sie im React 19 Upgrade Guide.
Wissenswert: Wenn Sie TypeScript verwenden, stellen Sie sicher, dass Sie auch
@types/react
und@types/react-dom
auf ihre neuesten Versionen aktualisieren.
Asynchrone Request-APIs (Breaking Change)
Zuvor synchrone Dynamic-APIs, die auf Laufzeitinformationen basieren, sind nun asynchron:
cookies
headers
draftMode
params
inlayout.js
,page.js
,route.js
,default.js
,opengraph-image
,twitter-image
,icon
undapple-icon
.searchParams
inpage.js
Um die Migration zu erleichtern, steht ein Codemod zur Verfügung, der den Prozess automatisiert, und die APIs können vorübergehend synchron aufgerufen werden.
cookies
Empfohlene asynchrone Verwendung
import { cookies } from 'next/headers'
// Vorher
const cookieStore = cookies()
const token = cookieStore.get('token')
// Nachher
const cookieStore = await cookies()
const token = cookieStore.get('token')
Temporäre synchrone Verwendung
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
// Vorher
const cookieStore = cookies()
const token = cookieStore.get('token')
// Nachher
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// gibt eine Warnung in der Entwicklung aus
const token = cookieStore.get('token')
import { cookies } from 'next/headers'
// Vorher
const cookieStore = cookies()
const token = cookieStore.get('token')
// Nachher
const cookieStore = cookies()
// gibt eine Warnung in der Entwicklung aus
const token = cookieStore.get('token')
headers
Empfohlene asynchrone Verwendung
import { headers } from 'next/headers'
// Vorher
const headersList = headers()
const userAgent = headersList.get('user-agent')
// Nachher
const headersList = await headers()
const userAgent = headersList.get('user-agent')
Temporäre synchrone Verwendung
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
// Vorher
const headersList = headers()
const userAgent = headersList.get('user-agent')
// Nachher
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// gibt eine Warnung in der Entwicklung aus
const userAgent = headersList.get('user-agent')
import { headers } from 'next/headers'
// Vorher
const headersList = headers()
const userAgent = headersList.get('user-agent')
// Nachher
const headersList = headers()
// gibt eine Warnung in der Entwicklung aus
const userAgent = headersList.get('user-agent')
draftMode
Empfohlene asynchrone Verwendung
import { draftMode } from 'next/headers'
// Vorher
const { isEnabled } = draftMode()
// Nachher
const { isEnabled } = await draftMode()
Temporäre synchrone Verwendung
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// Vorher
const { isEnabled } = draftMode()
// Nachher
// gibt eine Warnung in der Entwicklung aus
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
import { draftMode } from 'next/headers'
// Vorher
const { isEnabled } = draftMode()
// Nachher
// gibt eine Warnung in der Entwicklung aus
const { isEnabled } = draftMode()
params
& searchParams
Asynchrones Layout
// Vorher
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
}
// Nachher
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
}
// Vorher
export function generateMetadata({ params }) {
const { slug } = params
}
export default async function Layout({ children, params }) {
const { slug } = params
}
// Nachher
export async function generateMetadata({ params }) {
const { slug } = await params
}
export default async function Layout({ children, params }) {
const { slug } = await params
}
Synchrones Layout
// Vorher
type Params = { slug: string }
export default function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// Nachher
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
}
// Vorher
export default function Layout({ children, params }) {
const { slug } = params
}
// Nachher
import { use } from 'react'
export default async function Layout(props) {
const params = use(props.params)
const slug = params.slug
}
Asynchrone Seite
// Vorher
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
}
// Nachher
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
}
// Vorher
export function generateMetadata({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// Nachher
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
}
Synchrone Seite
'use client'
// Vorher
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
}
// Nachher
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
}
// Vorher
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// Nachher
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-Handler
// Vorher
type Params = { slug: string }
export async function GET(request: Request, segmentData: { params: Params }) {
const params = segmentData.params
const slug = params.slug
}
// Nachher
type Params = Promise<{ slug: string }>
export async function GET(request: Request, segmentData: { params: Params }) {
const params = await segmentData.params
const slug = params.slug
}
// Vorher
export async function GET(request, segmentData) {
const params = segmentData.params
const slug = params.slug
}
// Nachher
export async function GET(request, segmentData) {
const params = await segmentData.params
const slug = params.slug
}
runtime
-Konfiguration (Breaking Change)
Die runtime
-Segmentkonfiguration unterstützte zuvor einen Wert von experimental-edge
zusätzlich zu edge
. Beide Konfigurationen beziehen sich auf dasselbe, und um die Optionen zu vereinfachen, wird nun ein Fehler ausgegeben, wenn experimental-edge
verwendet wird. Um dies zu beheben, aktualisieren Sie Ihre runtime
-Konfiguration auf edge
. Ein Codemod steht zur Verfügung, um dies automatisch durchzuführen.
fetch
-Anfragen
fetch
-Anfragen werden standardmäßig nicht mehr zwischengespeichert.
Um bestimmte fetch
-Anfragen für das Caching zu aktivieren, können Sie die Option cache: 'force-cache'
übergeben.
export default async function RootLayout() {
const a = await fetch('https://...') // Nicht zwischengespeichert
const b = await fetch('https://...', { cache: 'force-cache' }) // Zwischengespeichert
// ...
}
Um alle fetch
-Anfragen in einem Layout oder einer Seite für das Caching zu aktivieren, können Sie die Segmentkonfigurationsoption export const fetchCache = 'default-cache'
verwenden. Wenn individuelle fetch
-Anfragen eine cache
-Option angeben, wird diese stattdessen verwendet.
// Da dies das Root-Layout ist, werden alle fetch-Anfragen in der App,
// die keine eigene cache-Option setzen, zwischengespeichert.
export const fetchCache = 'default-cache'
export default async function RootLayout() {
const a = await fetch('https://...') // Zwischengespeichert
const b = await fetch('https://...', { cache: 'no-store' }) // Nicht zwischengespeichert
// ...
}
Route-Handler
GET
-Funktionen in Route-Handlern werden standardmäßig nicht mehr zwischengespeichert. Um GET
-Methoden für das Caching zu aktivieren, können Sie eine Route-Konfigurationsoption wie export const dynamic = 'force-static'
in Ihrer Route-Handler-Datei verwenden.
export const dynamic = 'force-static'
export async function GET() {}
Client-seitiger Router-Cache
Bei der Navigation zwischen Seiten über <Link>
oder useRouter
werden Seiten-Segmente nicht mehr aus dem client-seitigen Router-Cache wiederverwendet. Sie werden jedoch weiterhin während der Browser-Navigation vorwärts und rückwärts sowie für gemeinsame Layouts wiederverwendet.
Um Seiten-Segmente für das Caching zu aktivieren, können Sie die staleTimes
-Konfigurationsoption verwenden:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfig
Layouts und Ladezustände werden weiterhin zwischengespeichert und bei der Navigation wiederverwendet.
next/font
Das @next/font
-Paket wurde zugunsten des integrierten next/font
entfernt. Ein Codemod steht zur Verfügung, um Ihre Importe sicher und automatisch umzubenennen.
// Vorher
import { Inter } from '@next/font/google'
// Nachher
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
experimental.bundlePagesExternals
ist nun stabil und wurde in bundlePagesRouterDependencies
umbenannt.
/** @type {import('next').NextConfig} */
const nextConfig = {
// Vorher
experimental: {
bundlePagesExternals: true,
},
// Nachher
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
experimental.serverComponentsExternalPackages
ist nun stabil und wurde in serverExternalPackages
umbenannt.
/** @type {import('next').NextConfig} */
const nextConfig = {
// Vorher
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// Nachher
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
Speed Insights
Die automatische Instrumentierung für Speed Insights wurde in Next.js 15 entfernt.
Um Speed Insights weiterhin zu verwenden, folgen Sie dem Vercel Speed Insights Quickstart-Guide.
NextRequest
Geolocation
Die Eigenschaften geo
und ip
in NextRequest
wurden entfernt, da diese Werte von Ihrem Hosting-Provider bereitgestellt werden. Ein Codemod ist verfügbar, um diese Migration zu automatisieren.
Falls Sie Vercel verwenden, können Sie alternativ die Funktionen geolocation
und ipAddress
aus @vercel/functions
nutzen:
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)
// ...
}