Migration depuis Create React App

Ce guide vous aidera à migrer un site existant créé avec Create React App vers Next.js.

Pourquoi migrer ?

Plusieurs raisons peuvent vous pousser à 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-side, aussi appelées applications monopages (SPA), souffrent souvent d'un temps de chargement initial lent. 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 de temps de chargement lent peut être partiellement résolu avec le découpage de code. Cependant, si vous essayez de le faire manuellement, vous risquez souvent de dégrader les performances. Il est facile d'introduire involontairement des cascades de requêtes réseau lors d'un découpage manuel. Next.js intègre un découpage de code automatique dans son routeur.

Cascades de requêtes réseau

Une cause fréquente de mauvaises performances survient lorsque les applications effectuent des requêtes client-serveur séquentielles pour récupérer des données. Un schéma courant dans une SPA consiste à afficher d'abord un contenu de substitution, puis à récupérer les données après que le composant a été monté. Malheureusement, cela signifie qu'un composant enfant qui récupère des données ne peut commencer qu'après que le composant parent ait fini de charger ses propres données.

Bien que Next.js prenne en charge la récupération de données côté client, il vous offre également la possibilité de déplacer cette récupération côté serveur, ce qui peut éliminer les cascades client-serveur.

États de chargement rapides et intentionnels

Avec la prise en charge intégrée du streaming via React Suspense, vous pouvez mieux contrôler quelles parties de votre interface utilisateur charger en premier et dans quel ordre, sans introduire de cascades réseau.

Cela vous permet de créer des pages qui se chargent plus rapidement et d'éliminer 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. Vous pouvez décider de récupérer les données au moment de la construction, à la demande côté serveur, ou côté client. Par exemple, vous pouvez récupérer des données depuis votre CMS et générer vos articles de blog au moment de la construction, ce qui permet ensuite une mise en cache efficace sur un CDN.

Middleware

Le Middleware Next.js vous permet d'exécuter du code côté serveur avant qu'une requête ne soit terminée. C'est particulièrement utile pour éviter d'afficher brièvement du contenu non authentifié lorsque l'utilisateur visite une page réservée aux utilisateurs connectés, en le redirigeant vers une page de connexion. Le middleware est aussi utile pour les tests et l'internationalisation.

Optimisations intégrées

Les images, polices et scripts tiers ont souvent un impact significatif sur les performances d'une application. Next.js inclut des composants qui les optimisent automatiquement pour vous.

Étapes de migration

Notre objectif avec cette migration 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 la conserverons comme une application purement côté client (SPA) sans migrer votre routeur existant. Cela minimise les risques de problèmes pendant la migration et réduit les conflits de fusion.

Étape 1 : Installer la dépendance Next.js

La première chose à faire est d'installer next comme dépendance :

Terminal
npm install next@latest

Étape 2 : Créer le fichier de configuration Next.js

Créez un fichier next.config.mjs à la racine de votre projet. Ce fichier contiendra vos options de configuration Next.js.

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // Génère une application monopage (SPA).
  distDir: './dist', // Change le répertoire de build en `./dist/`.
}

export default nextConfig

Étape 3 : Mettre à jour la configuration TypeScript

Si vous utilisez TypeScript, vous devez mettre à jour votre fichier tsconfig.json avec les modifications suivantes pour le rendre compatible avec Next.js :

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "baseUrl": ".",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "strictNullChecks": true
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    "./dist/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

Vous trouverez plus d'informations sur la configuration de TypeScript dans la documentation Next.js.

Étape 4 : Créer la mise en page racine

Une application utilisant le routeur App de Next.js doit inclure un fichier de mise en page racine, qui est un composant serveur React englobant toutes les pages de votre application. Ce fichier est défini au niveau supérieur du répertoire app.

L'équivalent le plus proche dans une application CRA est le fichier index.html, qui contient vos balises <html>, <head> et <body>.

Dans cette étape, vous allez convertir votre fichier index.html en un fichier de mise en page racine :

  1. Créez un nouveau répertoire app dans votre répertoire src.
  2. Créez un nouveau fichier layout.tsx dans ce répertoire app :
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return null
}
export default function RootLayout({ children }) {
  return null
}

Bon à savoir : Les extensions .js, .jsx ou .tsx peuvent être utilisées pour les fichiers de mise en page.

Copiez le contenu de votre fichier index.html dans le composant <RootLayout> précédemment créé, en remplaçant les balises body.div#root et body.script 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 : Nous ignorerons le fichier manifest, les icônes supplémentaires autres que le favicon, et la configuration des tests, mais si ces éléments sont nécessaires, Next.js les prend également en charge.

Étape 5 : Métadonnées

Next.js inclut déjà par défaut les balises meta charset et meta viewport, vous pouvez donc les supprimer de votre <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 tels que 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 pris en charge dans le répertoire app, vous pouvez supprimer leurs balises <link> :

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 modifications, vous êtes passé d'une déclaration manuelle dans votre index.html à une approche basée sur les conventions 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 6 : Styles

Comme Create React App, Next.js prend en charge nativement les CSS Modules.

Si vous utilisez un fichier CSS global, importez-le dans votre fichier app/layout.tsx :

import '../index.css'

// ...

Si vous utilisez Tailwind, vous devrez installer postcss et autoprefixer :

Terminal
npm install postcss autoprefixer

Puis, créez un fichier postcss.config.js à la racine de votre projet :

postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Étape 7 : Créer la page d'entrée

Dans Next.js, vous déclarez un point d'entrée pour votre application en créant un fichier page.tsx. L'équivalent le plus proche dans CRA est votre fichier src/index.tsx. Dans cette étape, vous allez configurer le point d'entrée de votre application.

Créez un répertoire [[...slug]] dans votre répertoire app.

Comme ce guide vise d'abord à configurer Next.js comme une SPA (Single Page Application), vous avez besoin que votre point d'entrée capture toutes les routes possibles de votre application. Pour cela, créez un nouveau répertoire [[...slug]] dans votre répertoire app.

Ce répertoire est ce qu'on appelle un segment de route attrape-tout optionnel. Next.js utilise un routeur basé sur le système de fichiers où les répertoires définissent les routes. Ce répertoire spécial garantira que toutes les routes de votre application seront dirigées vers son fichier page.tsx.

Créez un nouveau fichier page.tsx dans le répertoire app/[[...slug]] avec le contenu suivant :

import '../../index.css'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // Nous mettrons à jour ceci
}
import '../../index.css'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return '...' // Nous mettrons à jour ceci
}

Ce fichier est un composant serveur. Lorsque vous exécutez next build, le fichier est prérendu en un asset statique. Il ne nécessite aucun code dynamique.

Ce fichier importe notre CSS global et indique à generateStaticParams que nous allons uniquement générer une route, la route index à /.

Maintenant, déplaçons le reste de notre application CRA qui s'exécutera uniquement côté client.

'use client'

import React from 'react'
import dynamic from 'next/dynamic'

const App = dynamic(() => import('../../App'), { ssr: false })

export function ClientOnly() {
  return <App />
}
'use client'

import React from 'react'
import dynamic from 'next/dynamic'

const App = dynamic(() => import('../../App'), { ssr: false })

export function ClientOnly() {
  return <App />
}

Ce fichier est un composant client, défini par la directive 'use client'. Les composants clients sont toujours prérendus en HTML côté serveur avant d'être envoyés au client.

Comme nous voulons commencer avec une application purement côté client, nous pouvons configurer Next.js pour désactiver le prérendu à partir du composant App.

const App = dynamic(() => import('../../App'), { ssr: false })

Maintenant, mettez à jour votre page d'entrée pour utiliser le nouveau composant :

import '../../index.css'
import { ClientOnly } from './client'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return <ClientOnly />
}
import '../../index.css'
import { ClientOnly } from './client'

export function generateStaticParams() {
  return [{ slug: [''] }]
}

export default function Page() {
  return <ClientOnly />
}

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

Next.js gère les imports d'images statiques légèrement différemment de CRA. Avec CRA, importer un fichier image renvoie son URL publique sous forme de chaîne :

App.tsx
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. Lorsqu'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 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 éventuellement migrer 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 du nom de fichier. Par exemple, public/logo.png servira l'image à l'adresse /logo.png pour votre application, qui serait 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. Vous pouvez les ignorer pour l'instant. Elles seront résolues à la fin de ce guide.

Étape 9 : Migrer les variables d'environnement

Next.js prend en charge les variables d'environnement .env de manière similaire à CRA.

La principale différence est le préfixe utilisé pour exposer les variables d'environnement côté client. Changez toutes les variables d'environnement avec le préfixe REACT_APP_ en NEXT_PUBLIC_.

Étape 10 : Mettre à jour les scripts dans package.json

Vous devriez maintenant pouvoir exécuter votre application pour tester si vous avez migré avec succès vers Next.js. Mais avant cela, vous devez mettre à jour vos scripts dans votre package.json avec les commandes liées à Next.js, et ajouter .next, next-env.d.ts et dist à votre fichier .gitignore :

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
.gitignore
# ...
.next
next-env.d.ts
dist

Maintenant, exécutez npm run dev et ouvrez http://localhost:3000. Vous devriez voir votre application fonctionnant maintenant sur Next.js.

Étape 11 : Nettoyer

Vous pouvez maintenant nettoyer votre codebase des artefacts liés à Create React App :

  • Supprimez src/index.tsx
  • Supprimez public/index.html
  • Supprimez la configuration de reportWebVitals
  • Désinstallez les dépendances de CRA (react-scripts)

Compatibilité avec les bundlers

Create React App et Next.js utilisent par défaut webpack pour le bundling.

Lors de la migration de votre application CRA vers Next.js, vous pourriez avoir une configuration webpack personnalisée que vous souhaitez migrer. Next.js prend en charge la fourniture d'une configuration webpack personnalisée.

De plus, Next.js prend en charge Turbopack via next dev --turbo pour améliorer les performances de développement local. Turbopack prend également en charge certains loaders webpack pour la compatibilité et l'adoption progressive.

Prochaines étapes

Si tout s'est déroulé comme prévu, vous avez maintenant une application Next.js fonctionnelle exécutée comme une application monopage. Cependant, vous ne profitez pas encore de la plupart des avantages de Next.js, mais vous pouvez maintenant commencer à apporter des modifications progressives pour en tirer tous les bénéfices. Voici ce que vous pourriez faire ensuite :

Bon à savoir : L'utilisation d'une exportation statique ne prend pas actuellement en charge l'utilisation du hook useParams.