Menu

Version 15

Upgrade von 14 auf 15

Um auf Next.js Version 15 zu aktualisieren, können Sie den upgrade Codemod verwenden:

Terminal
npx @next/codemod@canary upgrade latest

Wenn Sie es manuell durchführen möchten, stellen Sie sicher, dass Sie die neuesten Next & React RC installieren, z.B.:

Terminal
npm i next@latest react@rc react-dom@rc eslint-config-next@latest

Hinweis:

  • Wenn Sie eine Peer-Dependencies-Warnung sehen, müssen Sie möglicherweise react und react-dom auf die empfohlenen Versionen aktualisieren oder die Flags --force oder --legacy-peer-deps verwenden, um die Warnung zu ignorieren. Dies wird nicht notwendig sein, sobald Next.js 15 und React 19 stabil sind.
  • Wenn Sie TypeScript verwenden, müssen Sie die React-Typen vorübergehend überschreiben. Weitere Informationen finden Sie im React 19 RC Upgrade-Leitfaden.

React 19

  • Die Mindestversionen von react und react-dom ist jetzt 19.
  • useFormState wurde durch useActionState ersetzt. Der useFormState Hook ist in React 19 noch verfügbar, aber veraltet und wird in einer zukünftigen Version entfernt. useActionState wird empfohlen und enthält zusätzliche Eigenschaften wie das direkte Lesen des pending Zustands. Mehr erfahren.
  • useFormStatus enthält jetzt zusätzliche Schlüssel wie data, method und action. Wenn Sie React 19 nicht verwenden, ist nur der pending Schlüssel verfügbar. Mehr erfahren.
  • Lesen Sie mehr im React 19 Upgrade-Leitfaden.

Asynchrone Anfrage-APIs (Breaking Change)

Zuvor synchrone Dynamic APIs, die von Laufzeitinformationen abhängen, sind jetzt asynchron:

Um die Migrationslast zu erleichtern, ist ein Codemod verfügbar, um den Prozess zu automatisieren, 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')
 
// Danach
const cookieStore = await cookies()
const token = cookieStore.get('token')

Temporäre synchrone Verwendung

app/page.tsx
TypeScript
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
 
// Vorher
const cookieStore = cookies()
const token = cookieStore.get('token')
 
// Danach
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// wird in der Entwicklung eine Warnung ausgeben
const token = cookieStore.get('token')

headers

Empfohlene asynchrone Verwendung

import { headers } from 'next/headers'
 
// Vorher
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// Danach
const headersList = await headers()
const userAgent = headersList.get('user-agent')

Temporäre synchrone Verwendung

app/page.tsx
TypeScript
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
 
// Vorher
const headersList = headers()
const userAgent = headersList.get('user-agent')
 
// Danach
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// wird in der Entwicklung eine Warnung ausgeben
const userAgent = headersList.get('user-agent')

draftMode

Empfohlene asynchrone Verwendung

import { draftMode } from 'next/headers'
 
// Vorher
const { isEnabled } = draftMode()
 
// Danach
const { isEnabled } = await draftMode()

Temporäre synchrone Verwendung

app/page.tsx
TypeScript
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
 
// Vorher
const { isEnabled } = draftMode()
 
// Danach
// wird in der Entwicklung eine Warnung ausgeben
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode

params & searchParams

Asynchrones Layout

app/layout.tsx
TypeScript
// 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
}
 
// Danach
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
}

Synchrones Layout

app/layout.tsx
TypeScript
// Vorher
type Params = { slug: string }
 
export default function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Params
}) {
  const { slug } = params
}
 
// Danach
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
}

Asynchrone Seite

app/page.tsx
TypeScript
// 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
}
 
// Danach
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
}

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
}
 
// Danach
import { use } from 'react'
 
type Params = Promise<{ slug: string }>
type SearchParams = { [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
}
 
// Danach
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

app/api/route.ts
// Vorher
type Params = { slug: string }
 
export async function GET(request: Request, segmentData: { params: Params }) {
  const params = segmentData.params
  const slug = params.slug
}
 
// Danach
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
// Vorher
export async function GET(request, segmentData) {
  const params = segmentData.params
  const slug = params.slug
}
 
// Danach
export async function GET(request, segmentData) {
  const params = await segmentData.params
  const slug = params.slug
}

Konfiguration von runtime (Breaking Change)

Die runtime Segment-Konfiguration unterstützte zuvor zusätzlich zu edge den Wert experimental-edge. Beide Konfigurationen beziehen sich auf dasselbe, und um die Optionen zu vereinfachen, wird jetzt ein Fehler ausgegeben, wenn experimental-edge verwendet wird. Um dies zu beheben, aktualisieren Sie Ihre runtime-Konfiguration auf edge. Ein Codemod ist verfügbar, um dies automatisch durchzuführen.

fetch-Anfragen

fetch-Anfragen werden standardmäßig nicht mehr zwischengespeichert.

Um bestimmte fetch-Anfragen für das Zwischenspeichern zu aktivieren, können Sie die Option cache: 'force-cache' übergeben.

app/layout.js
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 Zwischenspeichern zu aktivieren, können Sie die Segment-Konfigurationsoption export const fetchCache = 'default-cache' verwenden. Wenn einzelne fetch-Anfragen eine cache-Option angeben, wird diese stattdessen verwendet.

app/layout.js
// Da dies das Root-Layout ist, werden alle fetch-Anfragen in der App,
// die keine eigene Cache-Option festlegen, 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 Zwischenspeichern zu aktivieren, können Sie eine Route-Konfigurationsoption wie export const dynamic = 'force-static' in Ihrer Route-Handler-Datei verwenden.

app/api/route.js
export const dynamic = 'force-static'
 
export async function GET() {}

Client-seitiger Router-Cache

Bei der Navigation zwischen Seiten über <Link> oder useRouter werden Seiten nicht mehr aus dem Client-seitigen Router-Cache wiederverwendet. Sie werden jedoch weiterhin während der Browser-Rückwärts- und Vorwärtsnavigation und für gemeinsame Layouts wiederverwendet.

Um Seitensegmente für das Zwischenspeichern zu aktivieren, können Sie die Konfigurationsoption staleTimes verwenden:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
      static: 180,
    },
  },
}
 
module.exports = nextConfig

Layouts und Ladeanimationen werden weiterhin bei der Navigation zwischengespeichert und wiederverwendet.

next/font

Das @next/font-Paket wurde zugunsten des integrierten next/font entfernt. Ein Codemod ist verfügbar, um Ihre Importe sicher und automatisch umzubenennen.

app/layout.js
// Vorher
import { Inter } from '@next/font/google'
 
// Danach
import { Inter } from 'next/font/google'

bundlePagesRouterDependencies

experimental.bundlePagesExternals ist jetzt stabil und wurde in bundlePagesRouterDependencies umbenannt.

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Vorher
  experimental: {
    bundlePagesExternals: true,
  },
 
  // Danach
  bundlePagesRouterDependencies: true,
}
 
module.exports = nextConfig

serverExternalPackages

experimental.serverComponentsExternalPackages ist jetzt stabil und wurde in serverExternalPackages umbenannt.

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Vorher
  experimental: {
    serverComponentsExternalPackages: ['package-name'],
  },
 
  // Danach
  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 Leitfaden.

Geolocation von NextRequest

Die Eigenschaften geo und ip bei NextRequest wurden entfernt, da diese Werte von Ihrem Hosting-Anbieter bereitgestellt werden. Ein Codemod ist verfügbar, um diese Migration zu automatisieren.

Wenn Sie Vercel verwenden, können Sie alternativ die Funktionen geolocation und ipAddress aus @vercel/functions verwenden:

middleware.ts
import { geolocation } from '@vercel/functions'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  const { city } = geolocation(request)
 
  // ...
}
middleware.ts
import { ipAddress } from '@vercel/functions'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  const ip = ipAddress(request)
 
  // ...
}