Leçon 3 / 9
33%

Next.js 16 – Leçon 3 : Composants Serveur vs Client

Le modèle de rendu de Next.js 16

Dans Next.js 16, chaque composant React est soit un Server Component soit un Client Component. Par défaut, tout est Server Component. Vous devez explicitement opter pour le rendu client avec la directive "use client".

Client Component
'use client' ✅ useState, useEffect, hooks React ✅ onClick, onChange, événements DOM ✅ Accès au navigateur (window, localStorage) ❌ Pas d'accès direct à la DB ❌ Bundle JS envoyé au client
Server Component (défaut)
// Pas de directive nécessaire ✅ Lecture directe en base de données ✅ Accès au filesystem, variables secrètes ✅ Zéro JS envoyé au client ❌ Pas de hooks React (useState...) ❌ Pas d'événements DOM

Server Component : fetcher des données directement

Un Server Component peut lire une base de données, un fichier, ou appeler une API directement dans le composant, sans useEffect ni useState. Le HTML généré est envoyé tel quel au navigateur — aucun JS React n’est envoyé pour ce composant.

TypeScript app/blog/page.tsx — Server Component
// Pas de "use client" → Server Component par défaut

async function getPosts() {
  const res = await fetch('https://api.example.com/posts');
  return res.json();
}

export default async function BlogPage() {
  // await directement dans le composant — aucun useEffect !
  const posts = await getPosts();

  return (
    <ul className="space-y-4">
      {posts.map((post: { id: number; title: string }) => (
        <li key={post.id} className="p-4 border rounded-xl">
          {post.title}
        </li>
      ))}
    </ul>
  );
}
💡
async/await dans les composants

C’est la grande force des Server Components : le composant peut être async. Plus de useEffect(() => { fetch()... }, []) — vous écrivez du code asynchrone simple, comme dans n’importe quelle fonction Node.js.

Client Component : interactivité

Dès que vous avez besoin de useState, useEffect, d’événements comme onClick, ou d’accès au navigateur, ajoutez "use client" en haut du fichier.

TypeScript app/components/Counter.tsx — Client Component
'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div className="flex items-center gap-4">
      <button
        onClick={() => setCount(c => c - 1)}
        className="w-10 h-10 rounded-full bg-gray-200 font-bold hover:bg-gray-300"
      ></button>
      <span className="text-2xl font-mono w-12 text-center">{count}</span>
      <button
        onClick={() => setCount(c => c + 1)}
        className="w-10 h-10 rounded-full bg-gray-900 text-white font-bold hover:bg-gray-700"
      >+</button>
    </div>
  );
}

La règle clé : la frontière client

"use client" définit une frontière. Tout composant importé par un Client Component devient automatiquement client lui aussi. En revanche, un Server Component peut passer un Client Component en tant qu’enfant via children sans le « contaminer ».

TypeScript Pattern recommandé — « pousser le client vers les feuilles »
// ✅ BON : Server Component qui compose un Client Component
// app/blog/page.tsx (Server Component)
import LikeButton from '@/components/LikeButton'; // Client

export default async function Post() {
  const post = await fetchPost(); // côté serveur

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {/* Seul ce bouton est rendu côté client */}
      <LikeButton postId={post.id} />
    </article>
  );
}
⚠️
Ne pas mettre « use client » partout

Une erreur courante est d’ajouter "use client" à tous les composants « par sécurité ». Cela annule tous les bénéfices des Server Components : le JS est envoyé au client, les données ne peuvent plus être fetchées côté serveur. N’ajoutez "use client" que là où c’est vraiment nécessaire.

Quand utiliser quoi ?

Règle de décision
Besoin de useState / useEffect ?"use client"
Besoin de onClick, onChange ?"use client"
Besoin de window, localStorage ?"use client"
Besoin d'une lib client (framer-motion...) ?"use client"

Besoin de fetch / base de données ?Server Component
Composant statique / pas d'interactivité ?Server Component
Accès à des variables d'env secrètes ?Server Component
Pas sûr ?Server Component (défaut)

Exercice pratique

  • Créez un Server Component app/users/page.tsx qui fetche https://jsonplaceholder.typicode.com/users et affiche la liste des noms
  • Créez un Client Component SearchBar.tsx avec un useState pour filtrer la liste
  • Passez la liste depuis le Server Component au Client Component via les props
  • Ouvrez les DevTools → Network : observez qu’aucun fichier JS n’est envoyé pour le Server Component