~/wiki

Next.js (App Router)

Mis à jour le 2026-08-07Confiance : high
nextjsreactapp-routervercelssrfrontendtypescript

Démarrer

npx create-next-app@latest mon-app --ts --tailwind --app --no-src-dir
cd mon-app && npm run dev
▲ Next.js 16.3.0 (Turbopack)
- Local:   http://localhost:3000
✓ Ready in 812ms

Routage par fichiers

app/
  layout.tsx              → enveloppe toutes les pages
  page.tsx                → /
  globals.css
  blog/
    page.tsx              → /blog
    [slug]/page.tsx       → /blog/:slug
  api/
    items/route.ts        → /api/items
  (marketing)/            → groupe, n'apparaît pas dans l'URL
  loading.tsx             → état de chargement automatique
  error.tsx               → frontière d'erreur (doit être "use client")
  not-found.tsx           → 404

Server Components par défaut

Tout composant est serveur sauf mention contraire. Il peut être async, lire le système de fichiers, appeler une base — et son code n'est jamais envoyé au navigateur.

// app/items/page.tsx  — server component
import { readFileSync } from "node:fs";

export default async function Items() {
  const data = JSON.parse(readFileSync("content/items.json", "utf8"));
  return <ul>{data.map((i) => <li key={i.id}>{i.title}</li>)}</ul>;
}

"use client" en première ligne bascule un fichier côté navigateur. Nécessaire dès qu'on utilise useState, useEffect, un gestionnaire d'événement ou une API du DOM.

"use client";
import { useState } from "react";

export function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

Règle de composition : un composant serveur peut importer un composant client, l'inverse est impossible. Donc on descend "use client" le plus bas possible dans l'arbre.

Params et search params sont asynchrones

export default async function Page({
  params,
  searchParams,
}: {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ tag?: string }>;
}) {
  const { slug } = await params;
  const { tag } = await searchParams;
  return <h1>{slug} {tag}</h1>;
}

C'est le changement qui casse le plus de code venu des anciennes versions : params était un objet simple, il faut maintenant l'attendre.

Génération statique

export function generateStaticParams() {
  return getAllSlugs().map((slug) => ({ slug }));
}

export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = await params;
  return { title: `${slug} — Mon site` };
}

Avec generateStaticParams, chaque route est prérendue au build. Sans, elle est rendue à la demande.

Routes API

// app/api/items/route.ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  return NextResponse.json({ q: searchParams.get("q") });
}

export async function POST(request: Request) {
  const body = await request.json();
  return NextResponse.json({ ok: true, body }, { status: 201 });
}

Server Actions

// app/actions.ts
"use server";

export async function createItem(formData: FormData) {
  const title = formData.get("title") as string;
  await db.insert({ title });
  revalidatePath("/items");
}
import { createItem } from "./actions";

export default function Form() {
  return (
    <form action={createItem}>
      <input name="title" />
      <button type="submit">Créer</button>
    </form>
  );
}

Une mutation serveur appelée depuis le client sans écrire de route API.

import Link from "next/link";
import Image from "next/image";

<Link href="/blog/hello" prefetch>Article</Link>
<Image src="/photo.jpg" alt="" width={800} height={600} priority />
"use client";
import { useRouter, usePathname, useSearchParams } from "next/navigation";

const router = useRouter();
router.push("/blog");
router.refresh();     // recharge les données serveur sans perdre l'état client

next/navigation, pas next/router — ce dernier appartient au Pages Router.

Variables d'environnement

DATABASE_URL=postgres://...      # serveur uniquement
NEXT_PUBLIC_API_URL=https://...  # exposée au navigateur

Seul le préfixe NEXT_PUBLIC_ traverse vers le client. Tout le reste reste serveur — c'est la protection à ne pas contourner par confort.

Build et déploiement

npm run build        # vérifie les types et prérend
npm start            # sert le build de production
npx vercel --prod    # déploie
Route (app)
┌ ○ /                    142 B    102 kB
├ ● /blog/[slug]         1.2 kB   118 kB
└ ƒ /api/items           0 B      0 B

○ Static  ● SSG  ƒ Dynamic

Lire cette table à chaque build : une route passée en ƒ alors qu'elle devrait être statique signale un appel dynamique involontaire — cookies(), headers() ou un fetch non caché.

See also