Aller au contenu principal
Ben Tiga Jean Vincent
Retour au blog
  • Next.js
  • Architecture
  • TypeScript

Structurer un projet Next.js App Router qui tient sur la durée

Les conventions de dossiers, de composants serveur et de récupération de données que j'applique sur tous mes projets Next.js depuis deux ans.

Ben Tiga Jean Vincent2 min de lecture

L'App Router de Next.js apporte une vraie liberté d'organisation, et c'est précisément le problème. Sans convention, un projet de six mois devient un labyrinthe de components/ à trois niveaux. Voici la structure que j'applique systématiquement.

Séparer la route de l'interface

Une règle simple : les fichiers dans app/ ne contiennent que de la composition et de la récupération de données. Aucun style, aucune logique d'interaction.

// app/[locale]/projets/page.tsx
export default async function ProjectsPage({ params }: Props) {
  const { locale } = await params;
  const projects = await getProjects(locale);
 
  return <ProjectGrid projects={projects} locale={locale} />;
}

Tout le reste vit dans components/. L'avantage se révèle au moment de réorganiser les URLs : déplacer une route ne touche plus au code d'interface.

Trois familles de composants

J'évite les arborescences profondes au profit de trois dossiers plats :

  • components/ui/ : primitives sans logique métier : Button, Card, Badge.
  • components/sections/ : blocs de page complets : Hero, SkillsSection.
  • components/layout/ : navigation, pied de page, fournisseurs de contexte.

Si un composant ne trouve pas sa place dans l'une des trois, c'est généralement qu'il mélange deux responsabilités.

Garder les composants clients aux extrémités

Le réflexe le plus coûteux est de marquer "use client" trop haut dans l'arbre. Tout ce qui est en dessous part alors dans le bundle navigateur.

La bonne approche consiste à descendre la frontière aussi bas que possible : un bouton de thème est un composant client, la page qui le contient reste serveur.

Le cas des animations

Framer Motion impose "use client". Plutôt que de rendre toute une section cliente, j'isole un composant Reveal minimal qui ne fait qu'animer son children :

"use client";
 
export function Reveal({ children, delay = 0 }: Props) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 24 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, margin: "-80px" }}
      transition={{ duration: 0.5, delay }}
    >
      {children}
    </motion.div>
  );
}

Le contenu passé en children est rendu côté serveur : il n'entre jamais dans le bundle.

Centraliser le contenu dans data/

Les textes, listes de projets et compétences vivent dans des fichiers TypeScript sous data/. C'est typé, versionné avec le code, et modifiable sans toucher à un seul composant. Pour un portfolio ou un site vitrine, c'est largement suffisant : un CMS devient utile seulement quand une personne non technique doit publier.

Ce que je ne fais plus

Deux habitudes abandonnées en route :

  1. Les barrel files index.ts partout. Ils cassent le tree-shaking et créent des dépendances circulaires silencieuses.
  2. Les hooks génériques trop tôt. Un useFetch maison est presque toujours moins bon que le cache natif de fetch dans les composants serveur.

La structure n'est pas une fin en soi : elle sert à ce qu'un développeur qui revient sur le projet six mois plus tard sache où chercher en moins de dix secondes.

Articles liés

4 min de lecture

Ma méthode pour un front-end Angular qui reste maintenable

Découpage par domaine, composants standalone, signaux contre RxJS, intercepteurs et détection de changement : les décisions que je prends au démarrage d'un projet Angular.

  • Angular
  • TypeScript
  • Architecture
  • Frontend
Lire l'article
4 min de lecture

Intégrer les API de paiement des agrégateurs sans se faire piéger

Mobile Money, cartes bancaires, agrégateurs multiples : le modèle unifié, l'idempotence et la réconciliation qui évitent les doubles débits en production.

  • Paiement
  • FinTech
  • Architecture
  • API
Lire l'article