BaliseTonSite

Bonnes Pratiques

Les habitudes des devs Next.js qui livrent des projets solides.

9.1Architecture de projet

Un projet bien organisé se comprend en un coup d'œil. Voici la structure recommandée :

Bashbash
app/
├── layout.tsx           # Layout racine
├── page.tsx             # Homepage
├── (marketing)/         # Groupe de routes (pas dans l'URL)
   ├── about/
   └── pricing/
├── (app)/               # Zone authentifiée
   ├── dashboard/
   └── settings/
└── api/                 # API Routes

components/
├── ui/                  # Composants génériques (Button, Card, Modal...)
├── forms/               # Composants de formulaires
└── layout/              # Header, Footer, Sidebar

lib/
├── utils.ts             # Fonctions utilitaires
├── constants.ts         # Constantes
└── types.ts             # Types TypeScript partagés

hooks/
└── use-media-query.ts   # Custom hooks

9.2Composants : la règle du "push down"

Tu te souviens de la frontière Server / Client du chapitre 4 ? La règle d'or : pousse les Client Components le plus bas possible dans l'arbre.

TSXtsx
// ❌ MAUVAIS : tout le composant est Client
'use client'

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

  return (
    <div>
      <h1>Super Produit</h1>           {/* <- statique */}
      <p>Description longue...</p>      {/* <- statique */}
      <img src="/product.jpg" />        {/* <- statique */}
      <ReviewsList />                   {/* <- statique */}
      
      {/* SEUL élément interactif ↓ */}
      <button onClick={() => setCount(c => c + 1)}>
        Ajouter au panier ({count})
      </button>
    </div>
  )
}
TSXtsx
// ✅ BON : seul le bouton est Client
// app/produit/page.tsx (Server Component)
import { AddToCartButton } from './add-to-cart-button'

export default function ProductPage() {
  return (
    <div>
      <h1>Super Produit</h1>
      <p>Description longue...</p>
      <img src="/product.jpg" />
      <ReviewsList />
      
      <AddToCartButton />  {/* ← Client isolé */}
    </div>
  )
}

// app/produit/add-to-cart-button.tsx
'use client'
import { useState } from 'react'

export function AddToCartButton() {
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Ajouter au panier ({count})
    </button>
  )
}

Résultat : 90 % de ta page reste en Server Component (pas de JS envoyé au navigateur), et seul le petit bouton interactif est en Client Component. Ton site est plus léger et plus rapide.

9.3Performance : les quick wins

Ces optimisations prennent 5 minutes chacune et ont un impact énorme :

Images

Utilise toujours next/image. Ajoute priority sur l'image above-the-fold (hero). Utilise le format WebP quand possible.

Polices

Utilise next/font au lieu de charger Google Fonts via un <link>. Zero layout shift garanti.

Imports dynamiques

Pour les gros composants pas toujours visibles (modal, graphique), utilise le lazy loading :

TSXtsx
import dynamic from 'next/dynamic'

// Le composant n'est chargé QUE quand il est affiché
const HeavyChart = dynamic(() => import('./heavy-chart'), {
  loading: () => <p>Chargement du graphique...</p>,
})

// Usage normal
export default function StatsPage() {
  return (
    <div>
      <h1>Statistiques</h1>
      <HeavyChart />
    </div>
  )
}

9.4Gestion des erreurs

Un bon site ne crash pas. Il gère les erreurs avec élégance :

TSXtsx
// app/error.tsx - Erreur globale
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  return (
    <div className="text-center py-20">
      <h2 className="text-2xl font-bold mb-4">
        Oups, quelque chose s'est mal passé !
      </h2>
      <p className="text-slate-600 mb-6">{error.message}</p>
      <button
        onClick={reset}
        className="bg-blue-600 text-white px-6 py-2 rounded-lg"
      >
        Réessayer
      </button>
    </div>
  )
}

// app/not-found.tsx - Page 404
export default function NotFound() {
  return (
    <div className="text-center py-20">
      <h2 className="text-4xl font-bold mb-4">404</h2>
      <p className="text-slate-600">
        Cette page n'existe pas (encore ?).
      </p>
    </div>
  )
}

9.5Sécurité de base

Quelques règles simples pour éviter les galères :

RèglePourquoiComment
Valide les entréesInjection, XSSZod dans les Server Actions
Pas de secrets côté clientVol de clés APIPas de NEXT_PUBLIC_ pour les secrets
Protège les API RoutesAccès non autoriséVérifie l'auth dans chaque handler
Sanitize le HTMLXSSJamais de dangerouslySetInnerHTML sur du contenu user
TSXtsx
// Server Action sécurisée avec Zod
'use server'

import { z } from 'zod'

const ContactSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  message: z.string().min(10).max(1000),
})

export async function sendContact(formData: FormData) {
  const result = ContactSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
  })

  if (!result.success) {
    return { error: 'Données invalides' }
  }

  // ✅ Les données sont validées et typées
  await saveToDatabase(result.data)
  return { success: true }
}

9.6Le checklist avant de publier

Avant chaque mise en production, passe par cette checklist :

  • npm run build passe sans erreur
  • Toutes les images utilisent next/image
  • Chaque page a un title et une description uniques
  • Les .env.local sont dans le .gitignore
  • Les Server Actions valident les entrées (Zod)
  • Les pages error.tsx et not-found.tsx existent
  • Lighthouse score 90+ sur Performance et Accessibilité
  • Le site est responsive (testé sur mobile)

Verifie tes acquis

5 questions pour valider ce chapitre

1. Que signifie le pattern "push down the client boundary" ?

Valide et sauvegarde ce chapitre

Ne perds pas le fil de ton apprentissage. Chaque QCM terminé sauvegarde ton score. Crée ton profil gratuitement pour débloquer toutes les évaluations du site et retrouver tes résultats plus tard.

Commencer l'aventure
Déjà membre ?Connecte-toi