Comment migrer de Create React App vers Next.js
Ce guide vous aidera à migrer un site existant créé avec Create React App (CRA) vers Next.js.
Pourquoi effectuer la migration ?
Plusieurs raisons peuvent vous inciter à passer de Create React App à Next.js :
Temps de chargement initial lent
Create React App utilise React exclusivement côté client. Les applications purement client, aussi appelées applications monopages (SPA), subissent souvent un temps de chargement initial lent. Cela s'explique par plusieurs facteurs :
- Le navigateur doit attendre que le code React et l'ensemble de votre bundle applicatif soient téléchargés et exécutés avant que votre code puisse envoyer des requêtes pour charger les données.
- Votre code applicatif grossit avec chaque nouvelle fonctionnalité et dépendance ajoutée.
Aucun découpage de code automatique
Le problème précédent des temps de chargement lents peut être partiellement résolu avec le découpage de code. Cependant, si vous essayez de le faire manuellement, vous risquez d'introduire involontairement des cascades de requêtes. Next.js intègre nativement un découpage de code automatique et un élagage d'arbre (tree-shaking) dans son routeur et son pipeline de build.
Cascades de requêtes réseau
Une cause fréquente de mauvaise performance survient lorsque les applications effectuent des requêtes client-serveur séquentielles pour récupérer des données. Un modèle courant de récupération de données dans une SPA consiste à afficher un placeholder, puis à récupérer les données après que le composant a été monté. Malheureusement, un composant enfant ne peut commencer à récupérer des données qu'après que son parent a fini de charger ses propres données, ce qui crée une "cascade" de requêtes.
Bien que la récupération de données côté client soit supportée dans Next.js, ce dernier vous permet aussi de déplacer cette récupération côté serveur. Cela élimine souvent complètement les cascades client-serveur.
États de chargement rapides et intentionnels
Avec le support natif du streaming via React Suspense, vous pouvez définir quelles parties de votre UI se chargent en premier et dans quel ordre, sans créer de cascades de requêtes.
Cela vous permet de construire des pages qui se chargent plus rapidement et d'éliminer les décalages de mise en page (layout shifts).
Choisissez votre stratégie de récupération de données
Selon vos besoins, Next.js vous permet de choisir votre stratégie de récupération de données au niveau de la page ou du composant. Par exemple, vous pourriez récupérer des données depuis votre CMS et pré-rendre des articles de blog au moment du build (SSG) pour des temps de chargement rapides, ou récupérer les données au moment de la requête (SSR) quand nécessaire.
Middleware
Le Middleware Next.js vous permet d'exécuter du code côté serveur avant qu'une requête ne soit complétée. Par exemple, vous pouvez éviter un flash de contenu non authentifié en redirigeant un utilisateur vers une page de login dans le middleware pour les pages nécessitant une authentification. Vous pouvez aussi l'utiliser pour des fonctionnalités comme les tests A/B, l'expérimentation et l'internationalisation.
Optimisations intégrées
Les images, polices et scripts tiers ont souvent un impact important sur les performances d'une application. Next.js inclut des composants et APIs spécialisés qui les optimisent automatiquement pour vous.
Étapes de migration
Notre objectif est d'obtenir une application Next.js fonctionnelle le plus rapidement possible, afin que vous puissiez ensuite adopter les fonctionnalités de Next.js progressivement. Pour commencer, nous traiterons votre application comme une application purement côté client (SPA) sans remplacer immédiatement votre routeur existant. Cela réduit la complexité et les conflits de fusion.
Note : Si vous utilisez des configurations avancées de CRA comme un champ
homepage
personnalisé dans votrepackage.json
, un service worker personnalisé ou des ajustements spécifiques à Babel/webpack, consultez la section Considérations supplémentaires à la fin de ce guide pour des conseils sur la réplication ou l'adaptation de ces fonctionnalités dans Next.js.
Étape 1 : Installer la dépendance Next.js
Installez Next.js dans votre projet existant :
npm install next@latest
Étape 2 : Créer le fichier de configuration Next.js
Créez un fichier next.config.ts
à la racine de votre projet (même niveau que votre package.json
). Ce fichier contient vos options de configuration Next.js.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export', // Génère une application monopage (SPA)
distDir: 'build', // Change le répertoire de build en `build`
}
export default nextConfig
Note : Utiliser
output: 'export'
signifie que vous effectuez une exportation statique. Vous n'aurez pas accès aux fonctionnalités serveur comme le SSR ou les APIs. Vous pouvez supprimer cette ligne pour profiter des fonctionnalités serveur de Next.js.
Étape 3 : Créer la mise en page racine
Une application Next.js utilisant le routeur App doit inclure un fichier mise en page racine (root layout), qui est un composant serveur React qui englobera toutes vos pages.
L'équivalent le plus proche du fichier de mise en page racine dans une application CRA est public/index.html
, qui inclut vos balises <html>
, <head>
et <body>
.
- Créez un nouveau répertoire
app
dans votre dossiersrc
(ou à la racine de votre projet si vous préférez avoirapp
à la racine). - Dans le répertoire
app
, créez un fichierlayout.tsx
(oulayout.js
) :
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
export default function RootLayout({ children }) {
return '...'
}
Maintenant, copiez le contenu de votre ancien index.html
dans ce composant <RootLayout>
. Remplacez body div#root
(et body noscript
) par <div id="root">{children}</div>
.
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Bon à savoir : Next.js ignore par défaut le
public/manifest.json
de CRA, les icônes supplémentaires et la configuration de test. Si vous en avez besoin, Next.js les prend en charge avec son API Metadata et sa configuration de test.
Étape 4 : Métadonnées
Next.js inclut automatiquement les balises <meta charset="UTF-8" />
et <meta name="viewport" content="width=device-width, initial-scale=1" />
, vous pouvez donc les supprimer de <head>
:
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Tous les fichiers de métadonnées comme favicon.ico
, icon.png
, robots.txt
sont automatiquement ajoutés à la balise <head>
de l'application tant qu'ils sont placés à la racine du répertoire app
. Après avoir déplacé tous les fichiers supportés dans le répertoire app
, vous pouvez supprimer leurs balises <link>
en toute sécurité :
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Enfin, Next.js peut gérer vos dernières balises <head>
avec l'API Metadata. Déplacez vos dernières informations de métadonnées dans un objet metadata
exporté :
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Avec ces changements, vous êtes passé de la déclaration de tout dans votre index.html
à l'approche conventionnelle de Next.js intégrée au framework (API Metadata). Cette approche vous permet d'améliorer plus facilement votre SEO et la partageabilité web de vos pages.
Étape 5 : Styles
Comme CRA, Next.js supporte nativement les CSS Modules. Il supporte aussi les imports CSS globaux.
Si vous avez un fichier CSS global, importez-le dans votre app/layout.tsx
:
import '../index.css'
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Si vous utilisez Tailwind CSS, consultez notre documentation d'installation.
Étape 6 : Créer la page d'entrée
Create React App utilise src/index.tsx
(ou index.js
) comme point d'entrée. Dans Next.js (routeur App), chaque dossier à l'intérieur du répertoire app
correspond à une route, et chaque dossier doit avoir un page.tsx
.
Comme nous voulons garder l'application comme une SPA pour l'instant et intercepter toutes les routes, nous utiliserons une route attrape-tout optionnelle.
- Créez un répertoire
[[...slug]]
dansapp
.
app
┣ [[...slug]]
┃ ┗ page.tsx
┣ layout.tsx
- Ajoutez ce qui suit à
page.tsx
:
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // Nous mettrons à jour ceci
}
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // Nous mettrons à jour ceci
}
Cela indique à Next.js de générer une seule route pour le slug vide (/
), mappant ainsi toutes les routes à la même page. Cette page est un composant serveur, pré-rendu en HTML statique.
Étape 7 : Ajouter un point d'entrée client uniquement
Ensuite, nous intégrerons le composant racine App de votre CRA dans un composant client pour que toute la logique reste côté client. Si c'est votre première fois avec Next.js, il est bon de savoir que les composants clients (par défaut) sont toujours pré-rendus côté serveur. Vous pouvez les considérer comme ayant la capacité supplémentaire d'exécuter du JavaScript côté client.
Créez un fichier client.tsx
(ou client.js
) dans app/[[...slug]]/
:
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
- La directive
'use client'
fait de ce fichier un composant client. - L'import dynamique avec
ssr: false
désactive le rendu côté serveur pour le composant<App />
, le rendant véritablement client uniquement (SPA).
Maintenant, mettez à jour votre page.tsx
(ou page.js
) pour utiliser votre nouveau composant :
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
Étape 8 : Mettre à jour les imports d'images statiques
Dans CRA, l'import d'un fichier image renvoie son URL publique sous forme de chaîne de caractères :
import image from './img.png'
export default function App() {
return <img src={image} />
}
Avec Next.js, les imports d'images statiques renvoient un objet. Cet objet peut ensuite être utilisé directement avec le composant <Image>
de Next.js, ou vous pouvez utiliser la propriété src
de l'objet avec votre balise <img>
existante.
Le composant <Image>
offre des avantages supplémentaires comme l'optimisation automatique des images. Le composant <Image>
définit automatiquement les attributs width
et height
de la balise <img>
résultante en fonction des dimensions de l'image. Cela évite les décalages de mise en page lors du chargement de l'image. Cependant, cela peut poser problème si votre application contient des images dont une seule dimension est stylisée sans que l'autre ne soit définie sur auto
. Si elle n'est pas stylisée sur auto
, la dimension prendra par défaut la valeur de l'attribut de dimension de la balise <img>
, ce qui peut entraîner une distorsion de l'image.
Conserver la balise <img>
réduira le nombre de modifications dans votre application et évitera les problèmes mentionnés ci-dessus. Vous pourrez ensuite migrer ultérieurement vers le composant <Image>
pour profiter de l'optimisation des images en configurant un loader, ou en passant au serveur Next.js par défaut qui offre une optimisation automatique des images.
Convertissez les chemins d'import absolus pour les images importées depuis /public
en imports relatifs :
// Avant
import logo from '/logo.png'
// Après
import logo from '../public/logo.png'
Passez la propriété src
de l'image au lieu de l'objet image entier à votre balise <img>
:
// Avant
<img src={logo} />
// Après
<img src={logo.src} />
Alternativement, vous pouvez référencer l'URL publique de l'image basée sur le nom du fichier. Par exemple, public/logo.png
servira l'image à l'adresse /logo.png
pour votre application, qui sera la valeur de src
.
Avertissement : Si vous utilisez TypeScript, vous pourriez rencontrer des erreurs de type lors de l'accès à la propriété
src
. Pour les corriger, vous devez ajouternext-env.d.ts
au tableauinclude
de votre fichiertsconfig.json
. Next.js générera automatiquement ce fichier lorsque vous exécuterez votre application à l'étape 9.
Étape 9 : Migrer les variables d'environnement
Next.js prend en charge les variables d'environnement de manière similaire à CRA, mais requiert un préfixe NEXT_PUBLIC_
pour toute variable que vous souhaitez exposer dans le navigateur.
La principale différence réside dans le préfixe utilisé pour exposer les variables d'environnement côté client. Remplacez toutes les variables d'environnement avec le préfixe REACT_APP_
par NEXT_PUBLIC_
.
Étape 10 : Mettre à jour les scripts dans package.json
Mettez à jour vos scripts package.json
pour utiliser les commandes Next.js. Ajoutez également .next
et next-env.d.ts
à votre .gitignore
:
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
# ...
.next
next-env.d.ts
Vous pouvez maintenant exécuter :
npm run dev
Ouvrez http://localhost:3000. Vous devriez voir votre application fonctionnant désormais sur Next.js (en mode SPA).
Étape 11 : Nettoyer
Vous pouvez maintenant supprimer les artefacts spécifiques à Create React App :
public/index.html
src/index.tsx
src/react-app-env.d.ts
- La configuration
reportWebVitals
- La dépendance
react-scripts
(désinstallez-la depackage.json
)
Considérations supplémentaires
Utilisation d'un homepage
personnalisé dans CRA
Si vous avez utilisé le champ homepage
dans votre package.json
CRA pour servir l'application sous un sous-chemin spécifique, vous pouvez le reproduire dans Next.js en utilisant la configuration basePath
dans next.config.ts
:
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
basePath: '/my-subpath',
// ...
}
export default nextConfig
Gestion d'un Service Worker
personnalisé
Si vous avez utilisé le service worker de CRA (par exemple, serviceWorker.js
de create-react-app
), vous pouvez apprendre à créer des applications web progressives (PWA) avec Next.js.
Proxy des requêtes API
Si votre application CRA utilisait le champ proxy
dans package.json
pour rediriger les requêtes vers un serveur backend, vous pouvez le reproduire avec les rewrites de Next.js dans next.config.ts
:
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://your-backend.com/:path*',
},
]
},
}
Configuration personnalisée de Webpack / Babel
Si vous aviez une configuration personnalisée de webpack ou Babel dans CRA, vous pouvez étendre la configuration de Next.js dans next.config.ts
:
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
webpack: (config, { isServer }) => {
// Modifiez la configuration webpack ici
return config
},
}
export default nextConfig
Remarque : Cela nécessitera de désactiver Turbopack en supprimant
--turbopack
de votre scriptdev
.
Configuration TypeScript
Next.js configure automatiquement TypeScript si vous avez un tsconfig.json
. Assurez-vous que next-env.d.ts
est listé dans le tableau include
de votre tsconfig.json
:
{
"include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}
Compatibilité des bundlers
Create React App et Next.js utilisent par défaut webpack pour le bundling. Next.js propose également Turbopack pour un développement local plus rapide avec :
next dev --turbopack
Vous pouvez toujours fournir une configuration webpack personnalisée si vous avez besoin de migrer des configurations webpack avancées depuis CRA.
Prochaines étapes
Si tout a fonctionné, vous avez maintenant une application Next.js fonctionnelle exécutée en tant qu'application monopage. Vous n'utilisez pas encore les fonctionnalités de Next.js comme le rendu côté serveur ou le routage basé sur les fichiers, mais vous pouvez maintenant le faire progressivement :
- Migrez de React Router vers le routeur d'application Next.js pour :
- Le découpage de code automatique
- Le rendu côté serveur en streaming
- Les composants serveur React
- Optimisez les images avec le composant
<Image>
- Optimisez les polices avec
next/font
- Optimisez les scripts tiers avec le composant
<Script>
- Activez ESLint avec les règles recommandées par Next.js en exécutant
npx next lint
et en le configurant selon les besoins de votre projet
Remarque : L'utilisation d'une exportation statique (
output: 'export'
) ne prend pas actuellement en charge le hookuseParams
ou d'autres fonctionnalités serveur. Pour utiliser toutes les fonctionnalités de Next.js, supprimezoutput: 'export'
de votrenext.config.ts
.