Pourquoi Migrer de Create React App vers Next.js ?
La migration d'un projet React existant de Create React App (CRA) vers Next.js peut sembler intimidante, mais elle est en réalité une opportunité de tirer parti des nombreuses fonctionnalités avancées et des meilleures pratiques qui rendent Next.js le choix idéal pour les applications modernes. En effet, CRA est bien adapté aux petites applications ou aux petits projets individuels, tandis que Next.js offre une grande flexibilité, un meilleur SEO, et la possibilité de créer des applications universelles (SSR, SSG, ISR).
Un cas d'usage concret serait le développement d'une application de blogging. Avec CRA, vous pourriez avoir du mal à gérer efficacement les routes dynamiques, tandis que Next.js permettrait d'avoir une structure de route claire et des pages dynamiques facilement configurables.
Prerequis
- Connaissances en React
- Familiarité avec JavaScript ES6+
- Installation de Node.js (v14.0.0 ou plus)
- Installation de npm (Node Package Manager)
Concepts fondamentaux
1. Pages
Next.js utilise les fichiers du répertoire pages pour définir la structure des routes de votre application. Chaque fichier dans ce répertoire correspond à une route.
// pages/index.js
export default function Home() {
return <div>Bienvenue sur le Blog</div>;
}
2. Styles
Next.js supporte les fichiers CSS et SCSS directement dans les composants React.
// pages/about.js
import styles from '../styles/About.module.css';
export default function About() {
return (
<div className={styles.container}>
<h1>A propos</h1>
<p>Next.js est génial !</p>
</div>
);
}
3. Routing
Les routes sont automatiquement générées à partir des fichiers dans le répertoire pages.
// pages/blog/[slug].js
export default function Post({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
export async function getStaticPaths() {
// Fetch posts data from an API or a database
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { slug: post.slug },
}));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
// Fetch the data for this specific blog post
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return { props: { post } };
}
4. API Routes
Next.js vous permet de créer des routes d'API qui peuvent être appelées depuis le client ou directement par le serveur.
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' });
}
Mise en pratique : projet fil rouge
Nous allons créer un simple blog avec des routes dynamiques et une API pour récupérer les posts.
Étape 1 : Initialiser le Projet
npx create-next-app@latest nextjs-blog
cd nextjs-blog
Étape 2 : Créer les Composants
Créez un fichier components/Post.js pour afficher un post.
// components/Post.js
import styles from '../styles/Post.module.css';
export default function Post({ post }) {
return (
<div className={styles.post}>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
Étape 3 : Créer la Route Dynamique
Créez un fichier pages/blog/[slug].js pour afficher les détails d'un post.
// pages/blog/[slug].js
import Head from 'next/head';
import { useEffect, useState } from 'react';
import Post from '../components/Post';
export default function BlogPost({ initialPost }) {
const [post, setPost] = useState(initialPost);
useEffect(() => {
// Fetch post data if it's not already available
if (!initialPost) {
fetch(`https://api.example.com/posts/${slug}`)
.then((res) => res.json())
.then((data) => setPost(data));
}
}, [initialPost]);
return (
<div>
<Head>
<title>{post.title}</title>
</Head>
{post && <Post post={post} />}
</div>
);
}
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { slug: post.slug },
}));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
// Fetch the data for this specific blog post
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return { props: { initialPost: post } };
}
Étape 4 : Créer la Page d'Accueil
Créez un fichier pages/index.js pour afficher la liste des posts.
// pages/index.js
import Head from 'next/head';
import Link from 'next/link';
export default function Home({ posts }) {
return (
<div>
<Head>
<title>Blog</title>
</Head>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/blog/${post.slug}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
</div>
);
}
export async function getStaticProps() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return { props: { posts } };
}
Étape 5 : Créer l'API pour les Posts
Créez un fichier pages/api/posts.js pour récupérer la liste des posts.
// pages/api/posts.js
export default function handler(req, res) {
const posts = [
{ slug: 'post1', title: 'Premier Post', content: 'Contenu du premier post' },
{ slug: 'post2', title: 'Deuxième Post', content: 'Contenu du deuxième post' },
];
res.status(200).json(posts);
}
Erreurs frequentes et debugging
1. Error: Expected server HTML to contain a matching <div> in <div>
Cela se produit souvent lorsqu'il y a une différence entre le rendu côté serveur et le client.
// ❌ Mauvais
import React from 'react';
function MyComponent() {
return (
<div>
<h1>Hello World</h1>
</div>
);
}
export default MyComponent;
Corrigez-le en ajoutant une clé unique aux éléments.
// ✅ Correct
import React from 'react';
function MyComponent() {
return (
<div key="unique-key">
<h1>Hello World</h1>
</div>
);
}
export default MyComponent;
2. Error: A component must be returned from a render method, or null instead of undefined.
Cela se produit lorsque vous n'avez pas retourné une valeur à partir d'une fonction React.
// ❌ Mauvais
import React from 'react';
function MyComponent() {
if (condition) {
return <div>Hello World</div>;
}
}
Corrigez-le en ajoutant un else pour retourner null.
// ✅ Correct
import React from 'react';
function MyComponent({ condition }) {
if (condition) {
return <div>Hello World</div>;
} else {
return null;
}
}
export default MyComponent;
3. Error: Failed to load resource: the server responded with a status of 404 (Not Found)
Cela se produit lorsque le serveur ne trouve pas la route demandée.
// pages/blog/[slug].js
import { useEffect, useState } from 'react';
export default function BlogPost({ initialPost }) {
const [post, setPost] = useState(initialPost);
useEffect(() => {
if (!initialPost) {
fetch(`https://api.example.com/posts/${slug}`)
.then((res) => res.json())
.then((data) => setPost(data));
}
}, [initialPost]);
return (
<div>
{post && <h1>{post.title}</h1>}
</div>
);
}
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { slug: post.slug },
}));
return { paths, fallback: true };
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return { props: { initialPost: post } };
}
Pour aller plus loin
1. Navigation avec next/link
Utilisez le composant Link pour naviguer entre les pages.
// pages/index.js
import Link from 'next/link';
export default function Home() {
return (
<div>
<h1>Blog</h1>
<ul>
<li>
<Link href="/blog/post1">
<a>Premier Post</a>
</Link>
</li>
<li>
<Link href="/blog/post2">
<a>Deuxième Post</a>
</Link>
</li>
</ul>
</div>
);
}
2. Optimisation des Images avec next/image
Utilisez le composant Image pour optimiser les images.
// components/Post.js
import Image from 'next/image';
export default function Post({ post }) {
return (
<div className={styles.post}>
<h1>{post.title}</h1>
{post.image && (
<Image src={post.image} alt={post.title} width={500} height={300} />
)}
<p>{post.content}</p>
</div>
);
}
3. Hydratation Asynchrone avec getServerSideProps
Utilisez getServerSideProps pour récupérer les données côté serveur.
// pages/blog/[slug].js
export async function getServerSideProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return { props: { post } };
}
Défi Pratique
Créez un mini-projet de gestionnaire de tâches en utilisant Next.js. Le projet devrait permettre d'afficher une liste de tâches, d'ajouter de nouvelles tâches, et de marquer les tâches comme terminées.