Next.js : données et routes API
Lire des données côté serveur
Section intitulée « Lire des données côté serveur »export default async function Produits() { const reponse = await fetch('https://api.exemple.com/produits'); if (!reponse.ok) throw new Error('Chargement impossible'); const produits: { id: number; nom: string }[] = await reponse.json(); return ( <ul> {produits.map((p) => ( <li key={p.id}>{p.nom}</li> ))} </ul> );}throw déclenche la page error.tsx la plus proche (voir Routage avec l’App Router).
Mise en cache et revalidation
Section intitulée « Mise en cache et revalidation »La politique de cache de fetch a changé selon les versions de Next.js : depuis Next.js 15, les requêtes fetch ne sont plus mises en cache par défaut. Choisir explicitement :
await fetch(url, { cache: 'force-cache' }); // mis en cacheawait fetch(url, { next: { revalidate: 3600 } }); // mis en cache, rafraîchi toutes les heuresawait fetch(url, { next: { tags: ['produits'] } }); // étiqueté pour une revalidation cibléeawait fetch(url, { cache: 'no-store' }); // jamais mis en cacheRevalider à la demande, après une écriture :
'use server';import { revalidatePath, revalidateTag } from 'next/cache';
export async function apresModification() { revalidateTag('produits'); revalidatePath('/produits');}Les pages sans donnée dépendant de la requête sont statiques (construites au build) ; lire cookies(), headers() ou searchParams les rend dynamiques. La sortie de npm run build indique, pour chaque route, ○ (statique) ou ƒ (dynamique).
Les règles de cache évoluent d’une version à l’autre : vérifier le comportement avec la version installée (npm ls next).
Routes API : route handlers
Section intitulée « Routes API : route handlers »app/api/clients/route.ts :
import { NextResponse } from 'next/server';import { z } from 'zod';
const schema = z.object({ nom: z.string().min(1).max(100), email: z.string().email(),});
export async function GET(request: Request) { const { searchParams } = new URL(request.url); const page = Math.max(1, Number(searchParams.get('page')) || 1); const clients = await listerClients(page); return NextResponse.json({ page, donnees: clients });}
export async function POST(request: Request) { const corps = await request.json().catch(() => null); const resultat = schema.safeParse(corps); if (!resultat.success) { return NextResponse.json({ erreur: 'données invalides' }, { status: 400 }); } const client = await insererClient(resultat.data); return NextResponse.json(client, { status: 201 });}Route dynamique : app/api/clients/[id]/route.ts :
export async function GET( _request: Request, { params }: { params: Promise<{ id: string }> },) { const { id } = await params; const client = await trouverClient(Number(id)); if (!client) return NextResponse.json({ erreur: 'introuvable' }, { status: 404 }); return NextResponse.json(client);}Les fonctions portent le nom de la méthode HTTP (GET, POST, PUT, PATCH, DELETE).
curl -s http://127.0.0.1:3000/api/clients | jq .curl -s -X POST http://127.0.0.1:3000/api/clients -H 'Content-Type: application/json' \ -d '{"nom":"Alice","email":"alice@exemple.com"}'Accès à une base de données
Section intitulée « Accès à une base de données »Dans un composant serveur, un route handler ou une Server Action, accéder directement à la base :
import 'server-only';import { Pool } from 'pg';
const globale = globalThis as unknown as { pool?: Pool };export const pool = globale.pool ?? new Pool({ connectionString: process.env.DATABASE_URL, max: 10 });if (process.env.NODE_ENV !== 'production') globale.pool = pool;La mise en cache de globalThis évite de créer un nouveau pool à chaque rechargement en développement. Les requêtes sont toujours paramétrées ($1) : voir Express : API JSON. Pour MongoDB avec Mongoose : Mongoose : installation et connexion.
Authentification et middleware
Section intitulée « Authentification et middleware »Un fichier middleware.ts à la racine (ou src/) s’exécute avant les requêtes :
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) { const session = request.cookies.get('session')?.value; if (!session && request.nextUrl.pathname.startsWith('/admin')) { return NextResponse.redirect(new URL('/connexion', request.url)); } return NextResponse.next();}
export const config = { matcher: ['/admin/:path*'] };Dans les versions récentes de Next.js, ce fichier peut s’appeler proxy.ts : lire le guide de la version utilisée.
Pour l’authentification, utiliser une bibliothèque éprouvée (Auth.js, Better Auth…) plutôt que d’écrire la gestion des sessions et des mots de passe soi-même.
Appeler l’API depuis un composant client
Section intitulée « Appeler l’API depuis un composant client »'use client';import { useEffect, useState } from 'react';
export function ListeClients() { const [clients, setClients] = useState<{ id: number; nom: string }[]>([]); useEffect(() => { fetch('/api/clients') .then((r) => r.json()) .then((d) => setClients(d.donnees)); }, []); return ( <ul> {clients.map((c) => ( <li key={c.id}>{c.nom}</li> ))} </ul> );}Pour les données serveur à afficher, préférer un composant serveur : pas de requête supplémentaire depuis le navigateur.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
| Données périmées | Cache : cache: 'no-store', revalidate, revalidatePath |
| Données jamais rafraîchies en production | Page statique construite au build : rendre dynamique ou revalider |
Dynamic server usage au build |
Lecture de cookies()/headers() dans une page statique : export const dynamic = 'force-dynamic' |
ECONNREFUSED à la base au build |
Accès à la base pendant la construction : ne pas interroger la base dans une page statique ou rendre la page dynamique |
params est une promesse |
await params |
| Trop de connexions à la base en développement | Pool mis en cache sur globalThis |
405 Method Not Allowed |
Fonction de la méthode HTTP non exportée |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.