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 pousser à migrer de Create React App vers Next.js :

Temps de chargement initial lent

Create React App utilise React exclusivement côté client. Les applications uniquement côté client, aussi appelées applications monopages (SPA), subissent souvent des temps de chargement initiaux lents. Cela est dû à 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.

Pas de 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 réseau. Next.js fournit un découpage de code automatique et un élagage d'arbre intégrés à son routeur et 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 pour la récupération de données dans une SPA est de rendre un élément de substitution, puis de 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, Next.js 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 réseau.

Cela vous permet de construire des pages qui se chargent plus rapidement et éliminent les décalages de mise en page.

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 spécialisés et des APIs qui les optimisent automatiquement pour vous.

Étapes de migration

Notre objectif est d'obtenir une application Next.js fonctionnelle aussi rapidement que possible pour 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 de Babel/webpack, veuillez consulter 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', // Produit une application monopage (SPA)
  distDir: 'build', // Change le répertoire de build en `build`
}

export default nextConfig

Note : Utiliser output: 'export' signifie que vous faites 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 avec App Router doit inclure un fichier de mise en page racine, 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 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 '...'
}

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

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 supporte 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>
  )
}

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

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

Avec les changements ci-dessus, vous êtes passé de la déclaration de tout dans votre index.html à l'utilisation de 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 les CSS Modules nativement. 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 (App Router), 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
}

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

Étape 8 : Mettre à jour les imports d'images statiques

Dans CRA, importer un fichier image renvoie son URL publique sous forme de chaîne :

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 causer des problèmes si votre application contient des images dont une seule dimension est stylée sans que l'autre ne soit définie sur auto. Si elle n'est pas stylée sur auto, la dimension prendra par défaut la valeur de l'attribut de dimension de la balise <img>, ce qui peut déformer 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 en fonction de son nom de fichier. Par exemple, public/logo.png servira l'image à l'URL /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 est 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 les scripts de votre 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 de 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.

Proxification 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é du bundler

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 paramètres webpack avancés depuis CRA.

Prochaines étapes

Si tout a fonctionné, vous avez maintenant une application Next.js fonctionnelle 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 désormais le faire progressivement :

Remarque : L'utilisation d'une exportation statique (output: 'export') ne prend pas en charge actuellement 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.