Récupérez et affichez des données dynamiques

Professionnalisez la gestion de vos données

À 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 !

Créez un fichier de données centralisé

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 !

Importez vos données dans la page de liste

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 !

Utilisez les mêmes données dans la page de détail

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 :

  1. Centralisation des données : Plus besoin de dupliquer l'objet projects.

  2. 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

Générez le contenu statique

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. 

Comprenez l'appel aux APIs externes

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 !

À vous de jouer !

Avant de passer au chapitre suivant, appliquez ce que vous avez appris à la section Formations pour votre portfolio !

  1. 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).

  2. 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.

En résumé

  • 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.

Et si vous obteniez un diplôme OpenClassrooms ?
  • Formations jusqu’à 100 % financées
  • Date de début flexible
  • Projets professionnalisants
  • Mentorat individuel
Trouvez la formation et le financement faits pour vous