Pourquoi Next.js ?
Contexte réal : Un dev a besoin de Next.js pour construire des applications web modernes et performantes, qui offrent une expérience utilisateur fluide et rapide.
Un cas d'usage concret : Vous développez un site e-commerce avec des pages dynamiques comme les produits individuels et la panier d'achat. Avec Next.js, vous pouvez générer les pages statiques pour chaque produit au moment de sa publication et mettre à jour en temps réel lorsque le stock change.
Prerequis
Connaissances nécessaires :
- JavaScript ES6+
- HTML/CSS
- Connaissance des concepts de React (state, props, hooks)
Outils à installer :
- Node.js v14.0.0 ou supérieure
- npm v6.0.0 ou supérieure
Concepts fondamentaux
1. Pages et Routes
Définition : Next.js utilise les fichiers de pages pour définir les routes de l'application.
## pages/index.js
export default function Home() {
return <h1>Bienvenue sur mon site</h1>;
}
2. Routing dynamique
Définition : Permet d'afficher des pages selon un modèle.
## pages/posts/[id].js
import { useRouter } from 'next/router';
export default function Post() {
const router = useRouter();
const { id } = router.query;
return <h1>Post #{id}</h1>;
}
3. API Routes
Définition : Permet de créer des routes côté serveur pour traiter les requêtes HTTP.
## pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' });
}
4. Styles et CSS Modules
Définition : Permet de gérer les styles avec des fichiers .css ou .module.css.
## components/Button.module.css
.button {
padding: 10px 20px;
background-color: #6200ea;
color: white;
border-radius: 5px;
}
## components/Button.js
import styles from './Button.module.css';
export default function Button({ children }) {
return <button className={styles.button}>{children}</button>;
}
5. State Management
Définition : Utilisez le useState hook pour gérer l'état local des composants.
## pages/index.js
import { useState } from 'react';
export default function Home() {
const [count, setCount] = useState(0);
return (
<div>
<p>Compteur : {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementer</button>
</div>
);
}
6. Props
Définition : Permet de passer des données entre les composants.
## components/Hello.js
export default function Hello({ name }) {
return <h1>Bonjour {name}</h1>;
}
## pages/index.js
import Hello from '../components/Hello';
export default function Home() {
return (
<div>
<Hello name="John" />
</div>
);
}
7. Hooks personnalisés
Définition : Permet de créer des fonctions réutilisables avec le hook useEffect.
## hooks/useFetch.js
import { useState, useEffect } from 'react';
export default function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const result = await response.json();
setData(result);
setLoading(false);
}
fetchData();
}, [url]);
return { data, loading };
}
## pages/posts/[id].js
import useFetch from '../hooks/useFetch';
export default function Post() {
const { id } = useRouter();
const { data, loading } = useFetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
if (loading) return <div>Chargement...</div>;
return (
<div>
<h1>{data.title}</h1>
<p>{data.body}</p>
</div>
);
}
8. Static Generation
Définition : Permet de générer des pages statiques au build.
## pages/posts/[id].js
export async function getStaticPaths() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await response.json();
return {
paths: posts.map(post => ({ params: { id: post.id.toString() } })),
fallback: false,
};
}
export default function Post({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
export async function getStaticProps({ params }) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
const post = await response.json();
return { props: { post } };
}
Mise en pratique : Projet fil rouge
Projet : Gestionnaire de tâches
Initialisation du projet
npx create-next-app@latest task-manager cd task-managerCréation d'une page d'accueil
# pages/index.js export default function Home() { return ( <div> <h1>Task Manager</h1> <ul> <li><Link href="/tasks">Tâches</Link></li> <li><Link href="/add-task">Ajouter une tâche</Link></li> </ul> </div> ); }Création de la page des tâches
# pages/tasks.js import { useState } from 'react'; import Link from 'next/link'; export default function Tasks() { const [tasks, setTasks] = useState([]); const fetchTasks = async () => { const response = await fetch('https://jsonplaceholder.typicode.com/todos'); const data = await response.json(); setTasks(data); }; useEffect(() => { fetchTasks(); }, []); return ( <div> <h1>Tâches</h1> <ul> {tasks.map(task => ( <li key={task.id}> <Link href={`/tasks/${task.id}`}>{task.title}</Link> </li> ))} </ul> </div> ); }Création de la page d'une tâche
# pages/tasks/[id].js import { useRouter } from 'next/router'; export default function Task() { const router = useRouter(); const { id } = router.query; // Simuler une récupération de données à partir d'un API const task = { id, title: `Tâche ${id}`, description: `Description de la tâche ${id}` }; return ( <div> <h1>{task.title}</h1> <p>{task.description}</p> </div> ); }Création de la page pour ajouter une tâche
# pages/add-task.js import { useState } from 'react'; import Link from 'next/link'; export default function AddTask() { const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); // Simuler l'ajout de la tâche à un API console.log('Tâche ajoutée :', { title, description }); setTitle(''); setDescription(''); }; return ( <div> <h1>Ajouter une tâche</h1> <form onSubmit={handleSubmit}> <input type="text" placeholder="Titre" value={title} onChange={(e) => setTitle(e.target.value)} /> <textarea placeholder="Description" value={description} onChange={(e) => setDescription(e.target.value)} /> <button type="submit">Ajouter</button> </form> <Link href="/">Retourner à la liste des tâches</Link> </div> ); }
Erreurs fréquentes et debugging
1. Error: Element type is invalid
Code incorrect :
const MyComponent = (props) => {
return <h1>{props.title}</h1>;
};
export default MyComponent;
Code correct :
import React from 'react';
const MyComponent = ({ title }) => {
return <h1>{title}</h1>;
};
export default MyComponent;
2. Error: Function components cannot be given keys as children
Code incorrect :
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
<Task task={task} />
</li>
))}
</ul>
);
Code correct :
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
<Task {...task} />
</li>
))}
</ul>
);
3. Error: Can't perform a React state update on an unmounted component
Code incorrect :
useEffect(() => {
const interval = setInterval(() => {
setData(data + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
Code correct :
const [data, setData] = useState(0);
useEffect(() => {
let mounted = true;
const interval = setInterval(() => {
if (mounted) {
setData(data + 1);
}
}, 1000);
return () => {
mounted = false;
clearInterval(interval);
};
}, []);
Pour aller plus loin
Gestion des formulaires :
- Utiliser
useStatepour gérer les inputs etuseEffectpour traiter la soumission du formulaire. Documentation React Forms
- Utiliser
Optimisation des performances :
- Utiliser
React.memopour optimiser la réconciliation des composants. Documentation React Memo
- Utiliser
Intégration avec des bases de données :
- Utiliser
next-connectpour créer une API REST côté serveur et intégrer les données dans les pages Next.js. GitHub next-connect
- Utiliser
Défi pratique :
Créez un mini-projet d'une application de to-do list complète avec des fonctionnalités suivantes :
- Ajouter une tâche
- Supprimer une tâche
- Marquer une tâche comme terminée
Utilisez les concepts appris, notamment la gestion du state et les API Routes.