Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

7.4 — Méta-frameworks

🎯 Objectif : choisir lucidement entre rendu côté serveur, statique, hybride, edge — et utiliser Next.js (le plus répandu) en confiance.

À l'issue de cet axe, tu sauras :

  • Distinguer CSR, SSR, SSG, ISR, edge rendering — et savoir quand chacun est pertinent
  • Naviguer dans la structure App Router de Next.js
  • Connaître Nuxt, SvelteKit, Remix et leurs équivalents
  • Comprendre les server actions et la différence avec une API REST classique
  • Choisir entre Vercel, Netlify, Render, Cloudflare pour héberger

Débutant 10 min prérequis : axe 6 lu

Un framework pur (React, Vue) résout l’UI. Mais il te laisse choisir tout le reste : routing, SSR, data fetching, déploiement, optimisation des images, métadonnées, sitemap…

Un méta-framework intègre tout ça :

Sous-jacentMéta-framework
ReactNext.js (Vercel) — dominant
React (alternative SPA-first)TanStack Start (basé Vinxi + Vite, file-based routing typé)
VueNuxt
SvelteSvelteKit
React (focus loaders/actions)Remix
SolidSolidStart
Astro(Astro est son propre méta-framework)

C’est le concept clé à maîtriser.

flowchart TD
    CSR["CSR<br/>Client-Side Rendering<br/>HTML vide → JS dans le navigateur génère tout"]
    SSR["SSR<br/>Server-Side Rendering<br/>HTML généré au serveur À CHAQUE requête"]
    SSG["SSG<br/>Static Site Generation<br/>HTML généré au BUILD, servi tel quel"]
    ISR["ISR<br/>Incremental Static Regeneration<br/>SSG + revalidation périodique"]
    Edge["Edge rendering<br/>SSR mais sur un edge node proche de l'utilisateur"]
Les 5 modes de rendu — qui produit le HTML, quand
1. Navigateur reçoit un HTML vide + un gros bundle JS
2. JS s'exécute, fetch les données, génère le DOM
3. L'utilisateur voit quelque chose

✅ Simple à mettre en place. ❌ Mauvais SEO (Google voit du vide initial). ❌ Lent au premier affichage (LCP).

C’est ce que fait une SPA pure (Create React App, Vite + React).

1. Requête → serveur génère le HTML complet → renvoyé au client
2. Le client affiche le HTML immédiatement
3. JS s'hydrate ensuite pour rendre interactif

✅ SEO parfait. ✅ LCP rapide. ❌ Charge serveur à chaque requête. ❌ Latence de génération.

1. Au BUILD : on pré-génère toutes les pages en HTML
2. À la requête : on sert le fichier statique depuis un CDN

Plus rapide possible (CDN). ✅ Coût d’hébergement minimal. ❌ Données figées au build → pas adapté à du contenu très dynamique.

Idéal pour : blogs, marketing, docs (ce site est en SSG).

1. Comme SSG, mais avec revalidation périodique (ex. toutes les 60 s)
2. La 1re requête après expiration déclenche la regen
Next.js
export const revalidate = 60; // regénère après 60 s

✅ Compromis SSG + données fraîches. ✅ Tient des milliers de pages.

SSR exécuté sur un edge node (Cloudflare Workers, Vercel Edge Functions) proche de l’utilisateur.

✅ Latence très faible (~10 ms vs 100+ ms pour un serveur centralisé). ✅ Auto-scale mondial. ❌ Limites techniques (pas tous les modules Node, taille du bundle, durée d’exécution).

Type de pageMode idéal
Blog statiqueSSG
MarketingSSG
Liste produits e-commerceISR ou SSR
Dashboard admin persoSSR
Page de profil utilisateurSSR ou Edge
App temps réel (chat)CSR ou hybride

Depuis Next 13+, l’App Router (dossier app/) remplace le Pages Router (dossier pages/). Tout ce qui suit est en App Router.

  • Turbopack devient le bundler par défaut (Webpack n’est plus l’option principale).
  • L’accès synchrone à params est totalement supprimé : tu dois faire params: Promise<{ id: string }> puis await params. C’était deprecated en 15, c’est obligatoire en 16.
  • L’accès synchrone à cookies() et headers() est aussi supprimé : await cookies(), await headers().
  • Améliorations de streaming SSR et de caching granulaire.

Ces changements concernent toutes les pages dynamiques. Si tu migres une app Next 14, fais-le par lots : un dossier de routes à la fois.

app/
├── layout.tsx ← layout racine (head, body, providers)
├── page.tsx ← /
├── about/
│ └── page.tsx ← /about
├── blog/
│ ├── layout.tsx ← layout blog
│ ├── page.tsx ← /blog
│ └── [slug]/
│ └── page.tsx ← /blog/:slug
├── (marketing)/ ← grouping (n'apparaît PAS dans l'URL)
│ ├── pricing/
│ └── features/
└── api/
└── users/
└── route.ts ← API endpoint /api/users
app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr">
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);
}

Les layouts s’imbriquentapp/blog/layout.tsx enveloppe les pages de blog, à l’intérieur du layout racine.

app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
if (!product) notFound();
return <h1>{product.name}</h1>;
}
export const dynamic = 'force-dynamic'; // SSR
export const dynamic = 'force-static'; // SSG
export const revalidate = 60; // ISR avec 60 s

Par défaut, Next 14+ détecte automatiquement (si tu utilises des cookies ou des params dynamiques → SSR ; sinon → SSG).

// app/products/loading.tsx — pendant le chargement de la page
export default function Loading() {
return <Spinner />;
}
// app/products/error.tsx — si une erreur survient
'use client';
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<p>Erreur : {error.message}</p>
<button onClick={reset}>Réessayer</button>
</div>
);
}
// statique
export const metadata = {
title: 'Mon site',
description: 'Description de la page',
openGraph: { images: ['/og.png'] },
};
// dynamique
export async function generateMetadata({ params }) {
const post = await db.post.findUnique({ where: { id: params.id } });
return { title: post.title };
}
app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const users = await db.user.findMany();
return NextResponse.json(users);
}
export async function POST(request: Request) {
const data = await request.json();
const user = await db.user.create({ data });
return NextResponse.json(user, { status: 201 });
}
// middleware.ts (à la racine)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Protéger /admin
if (request.nextUrl.pathname.startsWith('/admin')) {
const session = request.cookies.get('session');
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/admin/:path*', '/api/admin/:path*'],
};

Au lieu d’écrire un endpoint POST + un client fetch, tu peux exposer une fonction serveur appelable directement depuis le client :

app/feedback/page.tsx
'use server';
async function sendFeedback(formData: FormData) {
'use server';
const message = formData.get('message') as string;
await db.feedback.create({ data: { message } });
}
export default function Page() {
return (
<form action={sendFeedback}>
<textarea name="message" required />
<button type="submit">Envoyer</button>
</form>
);
}

✅ Pas d’endpoint à écrire, pas de client fetch. ✅ Marche sans JS (le formulaire est natif). ✅ Validation côté serveur native.

Attention sécurité : une server action est appelable depuis n’importe où. Toujours valider et autoriser dedans (Zod + check de session).

nuxt-app/
├── pages/ ← file-based routing
│ ├── index.vue
│ └── products/[id].vue
├── components/
├── composables/ ← équivalent hooks
├── server/
│ └── api/
│ └── users.ts
└── nuxt.config.ts

Concepts identiques à Next.js mais en Vue. SSR, SSG, ISR via nitro (le moteur sous-jacent).

src/routes/
├── +layout.svelte
├── +page.svelte ← /
├── +page.server.ts ← load() côté serveur
└── products/[id]/
├── +page.svelte
└── +page.server.ts

Convention +page.svelte (UI) vs +page.server.ts (serveur). Très propre.

Approche anti-tendance par rapport à RSC : pas de Server Components, mais des loader() et action() clairs côté serveur, du HTML envoyé directement, et une approche très progressive enhancement.

// app/routes/products.$id.tsx
export async function loader({ params }) {
return await db.product.findUnique({ where: { id: params.id } });
}
export default function Product() {
const product = useLoaderData<typeof loader>();
return <h1>{product.name}</h1>;
}
export async function action({ request }) {
const form = await request.formData();
// ...
}

Très bien pour les apps qui doivent marcher sans JS. Maintenu par Shopify (ils ont absorbé Remix en 2022).

Tu construis un blog de 200 articles, mis à jour 1× par semaine. Mode de rendu idéal ?
Différence principale entre Server Action Next.js et endpoint API REST classique ?
Tu déploies une app Next.js avec auth + dashboard sur Vercel. Tu veux 80 % SSG (marketing) et 20 % SSR (dashboard utilisateur). Comment Next.js gère ça ?

Suite : 7.5 — Outils de build — ce qui se passe quand tu fais npm run build.