React - Next.js et les Meta-Frameworks

Une fois que tu maîtrises un peu React, je te CONSEILLE FORTEMENT de passer sur un meta-framework. Next.js, Astro, Remix... peu importe lequel, ça va révolutionner ta façon de développer !


Pourquoi des Meta-Frameworks ?

React seul, c'est limité

React, c'est juste une librairie pour créer des interfaces. Mais pour une vraie application web, il te faut :

  • 🗺️ Routing : gérer les pages et navigation
  • Server-Side Rendering (SSR) : SEO et performance
  • 🏗️ Static Site Generation (SSG) : sites ultra rapides
  • 🖼️ Optimisation d'images : formats modernes, lazy loading
  • 📦 Bundle optimization : code splitting automatique
  • 🔧 Configuration : Webpack, Babel, TypeScript...

React + Vite = Pas assez pour la prod

// ❌ Avec React/Vite classique - tu dois tout faire à la main
function App() {
  return (
    <div>
      {/* Comment tu fais du routing ? */}
      {/* Comment tu optimises les images ? */}
      {/* Comment tu gères le SEO ? */}
      {/* Comment tu fais du SSR ? */}
    </div>
  )
}
// ✅ Avec Next.js - tout est automatique !
export default function HomePage() {
  return (
    <div>
      <Head>
        <title>Ma super page - SEO automatique !</title>
      </Head>
      <Image 
        src="/photo.jpg" 
        alt="Photo optimisée automatiquement"
        width={500}
        height={300}
      />
    </div>
  )
}

// SSG automatique avec cette fonction
export async function getStaticProps() {
  const data = await fetch('https://api.example.com/data')
  return { props: { data } }
}

Next.js - Le Boss des Meta-Frameworks

Setup ultra simple

npx create-next-app@latest mon-app-nextjs
cd mon-app-nextjs
npm run dev

Boom ! Ton app Next.js tourne sur http://localhost:3000

Structure du projet

mon-app-nextjs/
├── app/                 # 🆕 App Router (Next.js 13+)
│   ├── layout.tsx       # Layout global
│   ├── page.tsx         # Page d'accueil
│   ├── about/
│   │   └── page.tsx     # Route /about
│   └── blog/
│       ├── page.tsx     # Route /blog
│       └── [id]/
│           └── page.tsx # Route dynamique /blog/123
├── public/              # Fichiers statiques
├── components/          # Tes composants
└── next.config.js       # Configuration Next.js

Les Super-Pouvoirs de Next.js

1. App Router - Routing Moderne

// app/page.tsx - Page d'accueil
export default function HomePage() {
  return <h1>Accueil</h1>
}

// app/about/page.tsx - Page /about
export default function AboutPage() {
  return <h1>À propos</h1>
}

// app/blog/[id]/page.tsx - Route dynamique
export default function BlogPost({ params }) {
  return <h1>Article {params.id}</h1>
}

// app/layout.tsx - Layout global
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <nav>
          <Link href="/">Accueil</Link>
          <Link href="/about">À propos</Link>
        </nav>
        {children}
      </body>
    </html>
  )
}

2. Server Components - Performance de malade

// ✅ Server Component - s'execute sur le serveur
async function ProductsList() {
  // Cette requête se fait côté serveur !
  const products = await fetch('https://api.example.com/products')
  
  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  )
}

// ✅ Client Component - pour l'interactivité
'use client'
import { useState } from 'react'

function ProductCard({ product }) {
  const [liked, setLiked] = useState(false)
  
  return (
    <div>
      <h3>{product.name}</h3>
      <button onClick={() => setLiked(!liked)}>
        {liked ? '❤️' : '🤍'}
      </button>
    </div>
  )
}

3. Static Site Generation (SSG)

// app/blog/[id]/page.tsx
export default function BlogPost({ params }) {
  return <h1>Article {params.id}</h1>
}

// Génère toutes les pages statiquement au build
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts')
  
  return posts.map(post => ({
    id: post.id.toString()
  }))
}

4. Server Actions - Full-stack sans API

// actions.ts
'use server'

export async function createPost(formData) {
  const title = formData.get('title')
  const content = formData.get('content')
  
  // Sauvegarde en base directement !
  await db.posts.create({ title, content })
  
  redirect('/blog')
}

// components/CreatePost.tsx
import { createPost } from './actions'

export default function CreatePost() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Titre" />
      <textarea name="content" placeholder="Contenu" />
      <button type="submit">Publier</button>
    </form>
  )
}

5. Optimisations Automatiques

import Image from 'next/image'
import Link from 'next/link'

export default function HomePage() {
  return (
    <div>
      {/* Image optimisée automatiquement */}
      <Image
        src="/hero.jpg"
        alt="Hero"
        width={800}
        height={400}
        priority // Charge en priorité
      />
      
      {/* Prefetch automatique des liens */}
      <Link href="/about">À propos</Link>
      
      {/* Code splitting automatique */}
      <DynamicComponent />
    </div>
  )
}

Exemples Pratiques

Blog avec SSG

// app/blog/page.tsx - Liste des articles
export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts')
  
  return (
    <div>
      <h1>Mon Blog</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>
            <Link href={`/blog/${post.id}`}>
              {post.title}
            </Link>
          </h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

// app/blog/[id]/page.tsx - Article individuel
export default async function BlogPost({ params }) {
  const post = await fetch(`https://api.example.com/posts/${params.id}`)
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

// Génération statique de tous les articles
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts')
  return posts.map(post => ({ id: post.id.toString() }))
}

E-commerce avec Server Actions

// app/products/[id]/page.tsx
import { addToCart } from './actions'

export default async function ProductPage({ params }) {
  const product = await fetch(`/api/products/${params.id}`)
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>Prix: {product.price}€</p>
      
      <form action={addToCart}>
        <input type="hidden" name="productId" value={product.id} />
        <input type="number" name="quantity" defaultValue={1} />
        <button type="submit">Ajouter au panier</button>
      </form>
    </div>
  )
}

// actions.ts
'use server'
import { cookies } from 'next/headers'

export async function addToCart(formData) {
  const productId = formData.get('productId')
  const quantity = formData.get('quantity')
  
  // Récupère le panier depuis les cookies
  const cart = cookies().get('cart')?.value || '[]'
  const cartItems = JSON.parse(cart)
  
  // Ajoute le produit
  cartItems.push({ productId, quantity: parseInt(quantity) })
  
  // Sauvegarde dans les cookies
  cookies().set('cart', JSON.stringify(cartItems))
  
  redirect('/cart')
}

Configuration Avancée

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Optimisations d'images
  images: {
    domains: ['example.com', 'cdn.example.com'],
    formats: ['image/webp', 'image/avif'],
  },
  
  // Variables d'environnement
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },
  
  // Redirections
  async redirects() {
    return [
      {
        source: '/old-page',
        destination: '/new-page',
        permanent: true,
      },
    ]
  },
  
  // Headers de sécurité
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
        ],
      },
    ]
  },
}

module.exports = nextConfig

Middleware pour l'auth

// middleware.ts
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Vérifie l'auth pour les pages protégées
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    const token = request.cookies.get('auth-token')
    
    if (!token) {
      return NextResponse.redirect(new URL('/login', request.url))
    }
  }
}

export const config = {
  matcher: '/dashboard/:path*',
}

Les Alternatives à Next.js

🚀 Astro - Le minimaliste

// Parfait pour les sites avec peu d'interactivité
---
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
---

<html>
<head>
  <title>Mon site Astro</title>
</head>
<body>
  <h1>Blog</h1>
  {posts.map(post => (
    <article>
      <h2>{post.title}</h2>
      <p>{post.excerpt}</p>
    </article>
  ))}
</body>
</html>

🎵 Remix - Le full-stack

// Remix mise tout sur les Web Standards
export async function loader({ params }) {
  return json(await getPost(params.id))
}

export async function action({ request }) {
  const formData = await request.formData()
  await createPost(formData)
  return redirect('/posts')
}

export default function Post() {
  const post = useLoaderData()
  
  return (
    <div>
      <h1>{post.title}</h1>
      <Form method="post">
        <input name="comment" />
        <button type="submit">Commenter</button>
      </Form>
    </div>
  )
}

⚡ Vite + React Router - DIY

// Si tu veux tout contrôler toi-même
import { BrowserRouter, Routes, Route } from 'react-router-dom'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/blog/:id" element={<BlogPost />} />
      </Routes>
    </BrowserRouter>
  )
}

Déployement

Vercel (Créateurs de Next.js)

npm i -g vercel
vercel --prod

Et voilà ! Ton app est en ligne avec HTTPS, CDN, edge functions...

Alternatives

  • Netlify : Simple et efficace
  • Railway : Full-stack avec base de données
  • AWS Amplify : Écosystème AWS complet
  • Self-hosted : Docker + votre serveur

Ressources Pour Aller Plus Loin