Comment mettre à jour vers la version 15
Mise à jour de la version 14 à la version 15
Pour mettre à jour vers Next.js version 15, vous pouvez utiliser le codemod upgrade
:
npx @next/codemod@canary upgrade latest
Si vous préférez le faire manuellement, assurez-vous d'installer les dernières versions de Next et React :
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
Bon à savoir :
- Si vous voyez un avertissement concernant les dépendances peer, vous devrez peut-être mettre à jour
react
etreact-dom
vers les versions suggérées, ou utiliser les flags--force
ou--legacy-peer-deps
pour ignorer l'avertissement. Ce ne sera plus nécessaire une fois que Next.js 15 et React 19 seront stables.
React 19
- Les versions minimales de
react
etreact-dom
sont désormais 19. useFormState
a été remplacé paruseActionState
. Le hookuseFormState
est toujours disponible dans React 19, mais il est déprécié et sera supprimé dans une future version.useActionState
est recommandé et inclut des propriétés supplémentaires comme la lecture directe de l'étatpending
. En savoir plus.useFormStatus
inclut désormais des clés supplémentaires commedata
,method
etaction
. Si vous n'utilisez pas React 19, seule la clépending
est disponible. En savoir plus.- Consultez le guide de mise à jour de React 19 pour plus d'informations.
Bon à savoir : Si vous utilisez TypeScript, assurez-vous également de mettre à jour
@types/react
et@types/react-dom
vers leurs dernières versions.
APIs de requête asynchrones (Changement cassant)
Les APIs dynamiques précédemment synchrones qui dépendent d'informations d'exécution sont désormais asynchrones :
cookies
headers
draftMode
params
danslayout.js
,page.js
,route.js
,default.js
,opengraph-image
,twitter-image
,icon
, etapple-icon
.searchParams
danspage.js
Pour faciliter la migration, un codemod est disponible pour automatiser le processus et les APIs peuvent temporairement être accédées de manière synchrone.
cookies
Utilisation asynchrone recommandée
import { cookies } from 'next/headers'
// Avant
const cookieStore = cookies()
const token = cookieStore.get('token')
// Après
const cookieStore = await cookies()
const token = cookieStore.get('token')
Utilisation synchrone temporaire
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
// Avant
const cookieStore = cookies()
const token = cookieStore.get('token')
// Après
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// affichera un avertissement en développement
const token = cookieStore.get('token')
import { cookies } from 'next/headers'
// Avant
const cookieStore = cookies()
const token = cookieStore.get('token')
// Après
const cookieStore = cookies()
// affichera un avertissement en développement
const token = cookieStore.get('token')
headers
Utilisation asynchrone recommandée
import { headers } from 'next/headers'
// Avant
const headersList = headers()
const userAgent = headersList.get('user-agent')
// Après
const headersList = await headers()
const userAgent = headersList.get('user-agent')
Utilisation synchrone temporaire
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
// Avant
const headersList = headers()
const userAgent = headersList.get('user-agent')
// Après
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// affichera un avertissement en développement
const userAgent = headersList.get('user-agent')
import { headers } from 'next/headers'
// Avant
const headersList = headers()
const userAgent = headersList.get('user-agent')
// Après
const headersList = headers()
// affichera un avertissement en développement
const userAgent = headersList.get('user-agent')
draftMode
Utilisation asynchrone recommandée
import { draftMode } from 'next/headers'
// Avant
const { isEnabled } = draftMode()
// Après
const { isEnabled } = await draftMode()
Utilisation synchrone temporaire
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// Avant
const { isEnabled } = draftMode()
// Après
// affichera un avertissement en développement
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
import { draftMode } from 'next/headers'
// Avant
const { isEnabled } = draftMode()
// Après
// affichera un avertissement en développement
const { isEnabled } = draftMode()
params
& searchParams
Layout asynchrone
// Avant
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
}
// Après
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
}
// Avant
export function generateMetadata({ params }) {
const { slug } = params
}
export default async function Layout({ children, params }) {
const { slug } = params
}
// Après
export async function generateMetadata({ params }) {
const { slug } = await params
}
export default async function Layout({ children, params }) {
const { slug } = await params
}
Layout synchrone
// Avant
type Params = { slug: string }
export default function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// Après
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
}
// Avant
export default function Layout({ children, params }) {
const { slug } = params
}
// Après
import { use } from 'react'
export default async function Layout(props) {
const params = use(props.params)
const slug = params.slug
}
Page asynchrone
// Avant
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
}
// Après
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
}
// Avant
export function generateMetadata({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// Après
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
}
Page synchrone
'use client'
// Avant
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
}
// Après
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
}
// Avant
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// Après
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
}
Gestionnaires de route
// Avant
type Params = { slug: string }
export async function GET(request: Request, segmentData: { params: Params }) {
const params = segmentData.params
const slug = params.slug
}
// Après
type Params = Promise<{ slug: string }>
export async function GET(request: Request, segmentData: { params: Params }) {
const params = await segmentData.params
const slug = params.slug
}
// Avant
export async function GET(request, segmentData) {
const params = segmentData.params
const slug = params.slug
}
// Après
export async function GET(request, segmentData) {
const params = await segmentData.params
const slug = params.slug
}
Configuration runtime
(Changement cassant)
La configuration runtime
segment configuration supportait auparavant une valeur experimental-edge
en plus de edge
. Les deux configurations se réfèrent à la même chose, et pour simplifier les options, nous générerons désormais une erreur si experimental-edge
est utilisé. Pour corriger cela, mettez à jour votre configuration runtime
vers edge
. Un codemod est disponible pour le faire automatiquement.
Requêtes fetch
Les requêtes fetch
ne sont plus mises en cache par défaut.
Pour activer la mise en cache pour des requêtes fetch
spécifiques, vous pouvez passer l'option cache: 'force-cache'
.
export default async function RootLayout() {
const a = await fetch('https://...') // Non mis en cache
const b = await fetch('https://...', { cache: 'force-cache' }) // Mis en cache
// ...
}
Pour activer la mise en cache pour toutes les requêtes fetch
dans un layout ou une page, vous pouvez utiliser l'option de configuration de segment export const fetchCache = 'default-cache'
segment config option. Si des requêtes fetch
individuelles spécifient une option cache
, celle-ci sera utilisée à la place.
// Comme il s'agit du layout racine, toutes les requêtes fetch dans l'application
// qui ne définissent pas leur propre option de cache seront mises en cache.
export const fetchCache = 'default-cache'
export default async function RootLayout() {
const a = await fetch('https://...') // Mis en cache
const b = await fetch('https://...', { cache: 'no-store' }) // Non mis en cache
// ...
}
Gestionnaires de route
Les fonctions GET
dans les Gestionnaires de route ne sont plus mises en cache par défaut. Pour activer la mise en cache pour les méthodes GET
, vous pouvez utiliser une option de configuration de route comme export const dynamic = 'force-static'
dans votre fichier de gestionnaire de route.
export const dynamic = 'force-static'
export async function GET() {}
Cache client-side du routeur
Lors de la navigation entre pages via <Link>
ou useRouter
, les segments de page ne sont plus réutilisés depuis le cache client-side du routeur. Cependant, ils sont toujours réutilisés lors de la navigation arrière et avant du navigateur et pour les layouts partagés.
Pour activer la mise en cache des segments de page, vous pouvez utiliser l'option de configuration staleTimes
:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfig
Les layouts et les états de chargement sont toujours mis en cache et réutilisés lors de la navigation.
next/font
Le package @next/font
a été supprimé au profit du next/font
intégré. Un codemod est disponible pour renommer automatiquement et en toute sécurité vos imports.
// Avant
import { Inter } from '@next/font/google'
// Après
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
experimental.bundlePagesExternals
est désormais stable et renommé en bundlePagesRouterDependencies
.
/** @type {import('next').NextConfig} */
const nextConfig = {
// Avant
experimental: {
bundlePagesExternals: true,
},
// Après
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
experimental.serverComponentsExternalPackages
est désormais stable et renommé en serverExternalPackages
.
/** @type {import('next').NextConfig} */
const nextConfig = {
// Avant
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// Après
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
Speed Insights
L'instrumentation automatique pour Speed Insights a été supprimée dans Next.js 15.
Pour continuer à utiliser Speed Insights, suivez le guide de démarrage rapide de Vercel Speed Insights.
Géolocalisation avec NextRequest
Les propriétés geo
et ip
de NextRequest
ont été supprimées car ces valeurs sont désormais fournies par votre hébergeur. Un codemod est disponible pour automatiser cette migration.
Si vous utilisez Vercel, vous pouvez utiliser à la place les fonctions geolocation
et ipAddress
du package @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)
// ...
}