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:

Terminal
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:

Terminal
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 und react-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 und react-dom sind nun 19.
  • useFormState wurde durch useActionState ersetzt. Der useFormState-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 des pending-Status. Mehr erfahren.
  • useFormStatus enthält nun zusätzliche Schlüssel wie data, method und action. Wenn Sie nicht React 19 verwenden, ist nur der pending-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:

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')

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')

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

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
}

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
}

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
}

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

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
}

// Nachher
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
}

// 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.

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 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.

app/layout.js
// 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.

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-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:

next.config.js
/** @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.

app/layout.js
// 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.

next.config.js
/** @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.

next.config.js
/** @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:

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)

  // ...
}