
À la fin du chapitre précédent, vous avez créé un portfolio avec plusieurs pages et des routes dynamiques pour vos projets. Si vous ouvrez le fichier app/projets/page.js, vous verrez que vos données sont écrites directement dans le code :
const projects = [
{ slug: 'portfolio', title: 'Portfolio Personnel', ... },
{ slug: 'ecommerce', title: 'App E-commerce', ... },
// ...
]Cette approche fonctionne, mais elle a des limites importantes :
Vous devez modifier le code à chaque ajout de projet
Les données ne peuvent pas être utilisées ailleurs
Impossible de les mettre à jour sans redéployer l'application
Difficile de faire évoluer la structure
Dans ce chapitre, vous allez apprendre à gérer vos données de manière professionnelle en les externalisant dans des fichiers JSON. C'est la première étape vers une vraie application dynamique !
Au lieu de répéter les mêmes données dans plusieurs fichiers, vous allez créer une source unique de vérité. Créez un nouveau dossier data à la racine de votre projet.
Créez ensuite le fichier data/projects.json
Ce fichier JSON contient quatre projets d'exemple structurés de manière professionnelle. Chaque projet a un identifiant unique (id), un slug pour l'URL, deux niveaux de description (court et long), et toutes les métadonnées nécessaires.
Remarquez le champ featured qui vous permettra plus tard de mettre en avant certains projets sur votre page d'accueil !
Maintenant que vos données sont centralisées, modifiez app/projets/page.js pour les importer :
import Link from 'next/link'
import styles from './page.module.css'
import projectsData from '@/data/projects.json'
import Tag from '@/components/Tag/Tag'
export default function Projects() {
return (
<div className={styles.container}>
<h1 className={styles.title}>Mes Projets</h1>
<p className={styles.description}>
Découvrez les projets sur lesquels j'ai travaillé
</p>
<div className={styles.grid}>
{projectsData.map((project) => (
<Link
href={`/projets/${project.slug}`}
key={project.id}
className={styles.card}
>
<div className={styles.imageWrapper}>
<img
src={project.image}
alt={project.title}
className={styles.image}
/>
</div>
<div className={styles.content}>
<h2>{project.title}</h2>
<p>{project.shortDescription}</p>
<div className={styles.tags}>
{project.tags.map((tech, index) => (
<Tag key={index} isDark={true}>{tech}</Tag>
))}
</div>
<span className={styles.viewMore}>Voir le projet →</span>
</div>
</Link>
))}
</div>
</div>
)
}Quelques changements importants ici :
L'import import projectsData from '@/data/projects.json' charge directement votre fichier JSON.
L'alias @/ est un raccourci configuré par Next.js qui pointe vers la racine de votre projet, donc @/data/projects.json = /data/projects.json depuis la racine.
Vous utilisez maintenant project.id comme key au lieu du slug, ce qui est plus robuste si jamais vous modifiez les slugs.
Les images sont maintenant affichées avec une vraie balise <img> (pour l'instant, nous optimiserons ça au prochain chapitre).
Testez : Rechargez /projets, vos quatre projets s'affichent maintenant avec leurs vraies images !
Maintenant que vos données sont centralisées, vous pouvez les réutiliser partout. Modifiez app/projets/[slug]/page.js :
import styles from "./page.module.css";
import projectsData from "@/data/projects.json";
import { notFound } from "next/navigation";
export default async function ProjectDetail({ params }) {
const { slug } = await params;
// Next.js passe automatiquement le slug dans params
const project = projectsData.find((project) => project.slug === slug);
// Si le projet n'existe pas, afficher la page 404
if (!project) {
notFound();
}
return (
<div className={styles.container}>
<div className={styles.header}>
<h1 className={styles.title}>{project.title}</h1>
<p className={styles.description}>{project.description}</p>
</div>
<div className={styles.content}>
<div className={styles.imageWrapper}>
<img
className={styles.image}
src={project.image}
alt={project.title}
/>
</div>
<div className={styles.details}>
<h2>Technologies utilisées</h2>
<div className={styles.technologies}>
{project.tags.map((tech, index) => (
<span key={index} className={styles.tech}>
{tech}
</span>
))}
</div>
<div className={styles.links}>
<a
href={project.github}
target="_blank"
rel="noopener noreferrer"
className={styles.link}
>
Voir le code →
</a>
<a
href={project.demo}
target="_blank"
rel="noopener noreferrer"
className={`${styles.link} ${styles.linkPrimary}`}
>
Voir la démo →
</a>
</div>
</div>
</div>
</div>
);
}Il y a deux améliorations majeures ici :
Centralisation des données : Plus besoin de dupliquer l'objet projects.
Gestion d'erreur robuste : La fonction notFound() de Next.js affiche automatiquement une belle page 404 si quelqu'un essaie d'accéder à un projet inexistant (comme /projets/projet-qui-nexiste-pas).
Maintenant pour bien comprendre ce qui se passe, lancez la commande :
npm run build
Nous reviendrons sur celle-ci dans le dernier chapitre, mais ici nous allons générer un build de production. Voici ce que vous pouvez lire a la fin des logs de votre terminal :
Route (app) ┌ ○ / ├ ○ /_not-found ├ ○ /a-propos ├ ○ /contact ├ ○ /formation ├ ƒ /formation/[slug] ├ ○ /projets └ ƒ /projets/[slug] ○ (Static) prerendered as static content ƒ (Dynamic) server-rendered on demand
A la fin de notre fichier /projets/[slug]/page.js ajoutons cette fonction :
// Cette fonction génère toutes les pages statiques au build
export function generateStaticParams() {
return projectsData.map((project) => ({
slug: project.slug,
}))
}La fonction generateStaticParams() travaille de manière differente suivant la phase du projet.
Durant le développement elle sera appelée au moment où la route est appelée par le navigateur. En production, elle génère le contenu html au moment du build ! Et donc pas besoin d’attendre la génération lorsque des personnes visitent votre page.
Dans cet exemple, vous utilisez un fichier JSON local. Mais dans un vrai projet professionnel, vos données pourraient venir d'une API externe, d'un CMS (comme Directus ou WordPress), ou d'une base de données.
La bonne nouvelle ? Next.js rend ça ultra-simple. Vos Server Components peuvent directement utiliser async / await pour récupérer des données. Voici un exemple de ce à quoi pourrait ressembler votre code avec une vraie API :
// Exemple avec une API externe (ne pas copier dans votre projet)
export default async function Projects() {
const response = await fetch('https://api.monportfolio.com/projects')
const projects = await response.json()
return (
<div>
{projects.map((project) => (
<div key={project.id}>{project.title}</div>
))}
</div>
)
}Vous avez remarqué le async avant function Projects() ?
Dans Next.js, vos Server Components peuvent être asynchrones ! Vous appelez directement await fetch() au début du composant, sans besoin de useEffect ou useState . C'est beaucoup plus simple et plus lisible.
Les avantages de cette approche :
Le code est plus court et plus clair
Le fetch se fait côté serveur (plus sécurisé)
Le HTML est généré avec les données (SEO optimal)
Pas d'écran blanc pendant le chargement
Pour l'instant, gardez votre fichier JSON local. C'est parfait pour apprendre et pour créer des maquettes rapidement. Plus tard, quand vous aurez besoin de données dynamiques (une vraie base de données, un CMS), vous n'aurez qu'à remplacer l'import par un fetch() !
Maintenant que vos données sont centralisées vous pourriez utiliser le fichier pour les ajouter à d’autres endroits de votre site, par exemple en ajoutant une section Projets qui affichera les projets mis en avant !

Avant de passer au chapitre suivant, appliquez ce que vous avez appris à la section Formations pour votre portfolio !
Modifiez la page app/formations/page.js qui liste toutes les formations présentes dans le fichierdata/formations.json(vous pouvez modifier le fichier pour y mettre vos formations).
Modifiez la page app/formations/[slug]/page.js pour afficher le détail de chaque formation provenant du fichier json.
Conseils :
Inspirez-vous de la structure des projets pour créer celle des formations
Réutilisez les styles existants et adaptez-les
N'oubliez pas d'ajouter generateStaticParams() dans la page de détail
Félicitations ! Vous maîtrisez maintenant la gestion de données dans Next.js.
Externaliser les données dans des fichiers JSON permet une meilleure maintenabilité.
Importer des fichiers JSON avec l'alias @/ permet d'avoir un code plus propre.
On peut réutiliser les données dans plusieurs pages sans duplication.
generateStaticParams() permet de pré-générer les pages et optimiser les performances.
notFound() permet de gérer élégamment les erreurs 404.
Tout le contenu est dans le HTML dès le départ, c'est parfait pour le référencement de votre site.