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 :

  1. 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.
  2. 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 votre package.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 :

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

next.config.ts
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>.

  1. Créez un nouveau répertoire app dans votre dossier src (ou à la racine de votre projet si vous préférez avoir app à la racine).
  2. Dans le répertoire app, créez un fichier layout.tsx (ou layout.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.

  1. Créez un répertoire [[...slug]] dans app.
app
 [[...slug]]
 page.tsx
 layout.tsx
  1. 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 ajouter next-env.d.ts au tableau include de votre fichier tsconfig.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 :

package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "npx serve@latest ./build"
  }
}
.gitignore
# ...
.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 de package.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 :

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 :

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 :

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

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 :

Remarque : L'utilisation d'une exportation statique (output: 'export') ne prend pas actuellement en charge le hook useParams ou d'autres fonctionnalités serveur. Pour utiliser toutes les fonctionnalités de Next.js, supprimez output: 'export' de votre next.config.ts.