Next.js
Créer un site Next.js pour la production : App Router, données, cache et déploiement
Créez un site Next.js prêt pour la production avec App Router, Server Components, cache explicite, mutations sécurisées, SEO, tests et déploiement.
Un site Next.js en production dépasse une collection de composants React. Le routage, les limites entre serveur et client, la durée de vie des données, les métadonnées, les états d’échec et le contrat de déploiement doivent être cohérents.
1. Définir ce que le site doit faire
Commencez par les responsabilités du site, pas par sa bibliothèque de composants. Listez les routes publiques et privées, les sources de contenu, les mutations, les besoins de recherche et les services externes. Vous saurez ainsi quelles pages peuvent être statiques, lesquelles ont besoin de données au moment de la requête et où vérifier les autorisations.
Sur un site produit classique, les pages marketing et la documentation changent assez lentement, le blog est généré à partir du contenu et l’espace de compte dépend de l’utilisateur connecté. Ces données ont trois durées de vie différentes. Tout rendre dans une application monopage côté client fait perdre le bénéfice du rendu serveur ; tout rendre en HTML statique rend l’espace de compte impossible.
Décrivez la carte des routes avant l’implémentation. Donnez à chaque page importante une fonction principale et une URL canonique. Décidez quelles entités nécessitent des segments dynamiques, comme /blog/[slug], et quelle interface doit persister pendant la navigation dans un layout commun.
- —Contenu public : accueil, produit, tarifs, à propos, blog, guides et pages juridiques.
- —Contenu applicatif : tableau de bord, paramètres, facturation ou autres routes dépendantes de la session.
- —Sources et limites des données : fichiers locaux, CMS, base de données, API externes et saisies utilisateur.
- —Besoins d’exploitation : variables d’environnement, contrôles de santé, journaux, tâches planifiées et déploiement.
2. Créer le projet avec des valeurs par défaut choisies
Ce guide utilise App Router de Next.js 16, TypeScript et un répertoire src. Les valeurs par défaut actuelles de create-next-app conviennent à un nouveau projet, mais des options explicites rendent la configuration reproductible pour l’équipe et la CI.
Lancez le serveur de développement, puis effectuez immédiatement une compilation de production. Une compilation dès le premier jour révèle les problèmes de version Node.js, les imports non pris en charge et les erreurs de configuration avant que le projet ne se construise autour. Gardez le fichier de verrouillage npm dans le dépôt et utilisez npm ci pour les compilations automatisées afin de rendre la résolution des dépendances reproductible.
Terminal
npx create-next-app@latest northstar \
--ts --tailwind --eslint --app --src-dir \
--import-alias "@/*"
cd northstar
npm run dev
npm run build3. Organiser les routes autour des parcours utilisateur
App Router transforme les dossiers en segments d’URL. Un fichier page.tsx rend une route publique ; layout.tsx enveloppe ce segment et ses descendants ; loading.tsx fournit un contenu d’attente en streaming ; error.tsx intercepte les erreurs de rendu non traitées ; not-found.tsx gère les ressources manquantes. Placez les fichiers près de la route à laquelle ils appartiennent plutôt que de créer un dossier global de composants sans limites claires.
Les groupes de routes comme (marketing) et (app) organisent les dossiers sans changer l’URL. Ils sont utiles si le site public et le produit authentifié exigent des layouts différents. Les segments dynamiques comme [slug] reçoivent des paramètres de route, tandis que les segments catch-all comme [...parts] capturent plusieurs niveaux.
Utilisez les routes parallèles et d’interception avancées uniquement si l’interaction le nécessite. Une page photo partageable qui s’ouvre en fenêtre modale pendant la navigation dans l’application convient ; une page classique de paramètres, non. L’arbre de routes le plus simple qui correspond au modèle mental de l’utilisateur est généralement le plus facile à déboguer.
A practical App Router structure
src/app/
├── layout.tsx
├── globals.css
├── (marketing)/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── pricing/page.tsx
│ └── blog/
│ ├── page.tsx
│ └── [slug]/page.tsx
├── (app)/
│ ├── dashboard/page.tsx
│ ├── settings/page.tsx
│ └── loading.tsx
├── api/health/route.ts
├── not-found.tsx
└── global-error.tsx4. Garder un layout racine léger et stable
Le layout racine est obligatoire et contient les éléments html et body. Placez-y ce qui est réellement global : langue du document, variables de polices du site, CSS global, fournisseur de thème si nécessaire et métadonnées par défaut communes. N’en faites pas un fourre-tout de requêtes propres à certaines routes ou un vaste arbre de fournisseurs côté client.
Les layouts persistent pendant la navigation côté client : ils conviennent donc à la navigation stable et à la structure commune. Un fichier template.tsx se comporte autrement : il reçoit une nouvelle clé et est remonté quand son segment change. Choisissez un template uniquement si la réinitialisation à la navigation est voulue, par exemple pour relancer une animation d’entrée ou réinitialiser l’état local.
src/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"], display: "swap" });
export const metadata: Metadata = {
metadataBase: new URL("https://example.com"),
title: { default: "Northstar", template: "%s | Northstar" },
description: "Planning software for focused product teams.",
};
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}5. Utiliser les Server Components par défaut
Les pages et layouts sont des Server Components, sauf si vous marquez le module avec la directive use client. Ils peuvent interroger une base de données, lire les variables d’environnement réservées au serveur, appeler des services internes et envoyer le résultat rendu sans ajouter leur code au bundle navigateur.
Ajoutez un Client Component au plus petit périmètre interactif utile. Un filtre peut nécessiter un état, des gestionnaires d’événement et les API d’URL ; la grille de produits située dessous peut rester un Server Component. Transformer toute la page en Client Component transfère au navigateur plus de JavaScript et de récupération de données que l’interaction ne l’exige.
Les props qui passent du serveur au client doivent être sérialisables. Transmettez un modèle de vue limité plutôt qu’un enregistrement de base avec des champs privés. Ajoutez un import server-only aux modules de données qui ne doivent jamais entrer dans un bundle client : une violation accidentelle de cette limite devient alors une erreur de compilation.
A small client island inside a server-rendered page
// src/app/products/page.tsx — Server Component
import { getProducts } from "@/lib/data";
import ProductFilters from "./product-filters";
export default async function ProductsPage() {
const products = await getProducts();
return <ProductFilters products={products} />;
}
// src/app/products/product-filters.tsx — Client Component
"use client";
import { useState } from "react";
export default function ProductFilters({ products }) {
const [query, setQuery] = useState("");
const visible = products.filter((product) =>
product.name.toLowerCase().includes(query.toLowerCase()),
);
return <>{/* input and product list */}</>;
}6. Récupérer les données là où elles sont rendues
Un Server Component asynchrone peut appeler directement fetch, un ORM ou un client de base de données. Il est donc inutile de créer un endpoint HTTP interne uniquement pour que le code rendu sur le serveur s’appelle lui-même. Gardez la requête dans un module d’accès aux données réservé au serveur si plusieurs routes l’utilisent ou si les autorisations et la sélection des données renvoyées doivent être centralisées pour l’audit.
Évitez les requêtes en cascade. Si deux requêtes sont indépendantes, lancez-les ensemble avec Promise.all. Si seul un composant enfant a besoin de données plus lentes, laissez-le les récupérer et placez une limite Suspense autour de lui. La page peut alors envoyer du HTML utile pendant que cette zone termine son chargement.
Décidez du niveau de fraîcheur nécessaire pour chaque résultat avant d’ajouter un cache. Les données de compte propres à un utilisateur doivent généralement être calculées au moment de la requête. Un tableau public de tarifs peut être mis en cache. Un catalogue de produits peut utiliser des tags de cache invalidés après la publication d’un changement par un éditeur.
Parallel server-side data fetching
import "server-only";
export default async function DashboardPage() {
const [account, activity] = await Promise.all([
getAccount(),
getRecentActivity(),
]);
return <Dashboard account={account} activity={activity} />;
}7. Traiter le cache comme un choix produit
Dans Next.js 16, Cache Components doit être activé explicitement. Quand cacheComponents est activé, la directive use cache peut mettre en cache une fonction ou un composant asynchrone. cacheLife définit sa durée de vie, cacheTag donne aux entrées liées une clé d’invalidation commune et une limite Suspense diffuse le travail non mis en cache effectué au moment de la requête, à côté de la structure déjà en cache.
Le cache n’est pas automatiquement correct parce que le contenu est public. Demandez-vous ce qui se passe si le résultat est périmé, comment il est invalidé, si le cache est partagé par l’hébergement et si une valeur de session peut entrer dans sa clé. Ne mettez jamais le résultat privé d’un utilisateur sous une clé qu’un autre peut recevoir.
Utilisez updateTag dans une Server Action si le même utilisateur doit voir immédiatement le résultat d’une mutation. Utilisez revalidateTag avec un profil de cache adapté si stale-while-revalidate est acceptable, ou revalidatePath si la limite d’invalidation correspond naturellement à une page ou un layout. Gardez l’invalidation près de la mutation qui rend les données en cache périmées.
next.config.ts and src/lib/products.ts
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = { cacheComponents: true };
export default nextConfig;
// src/lib/products.ts
import "server-only";
import { cacheLife, cacheTag } from "next/cache";
export async function getProducts() {
"use cache";
cacheLife("hours");
cacheTag("products");
return db.product.findMany();
}8. Prévoir les états de chargement, de résultat vide, d’erreur et de ressource manquante
Le parcours réussi n’est qu’un état parmi d’autres. loading.tsx crée une limite Suspense au niveau de la route et permet à la navigation d’afficher immédiatement un contenu d’attente. Ajoutez des limites Suspense plus petites pour révéler séparément les zones indépendantes. Un squelette utile préserve la mise en page finale au lieu de remplacer la page par un indicateur de chargement qui provoque un grand décalage.
Les échecs attendus doivent suivre le flux normal de contrôle. Une erreur de validation doit renvoyer un résultat typé au formulaire. Un enregistrement manquant doit appeler notFound. Un refus d’autorisation doit produire la réponse ou la redirection appropriée. Réservez error.tsx aux exceptions non interceptées, journalisez l’erreur avec assez de contexte sur la requête et la version pour enquêter, puis proposez à l’utilisateur une action de reprise sûre.
Un résultat vide n’est pas une erreur. Pour un nouveau compte sans projet, la page doit expliquer ce qu’elle contiendra et comment créer le premier projet. Cette distinction rend l’interface plus compréhensible et concentre la supervision sur les véritables échecs.
src/app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
export default async function PostPage({ params }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return <article>{/* post content */}</article>;
}9. Gérer explicitement les mutations et les endpoints HTTP
Les Server Actions conviennent directement aux mutations déclenchées par l’interface React. Marquez la fonction avec use server, validez FormData sur le serveur, vérifiez la session et les autorisations de l’utilisateur, effectuez l’écriture, puis invalidez le cache concerné. Traitez chaque Server Action exportée comme un endpoint public : cacher le bouton ne constitue pas un contrôle d’autorisation.
Utilisez les Route Handlers pour les interfaces HTTP qui dépassent un formulaire React, notamment les webhooks, contrôles de santé, réponses de fichiers et endpoints appelés par des applications mobiles ou des tiers. Exportez les gestionnaires des verbes HTTP requis et renvoyez des objets Web Response standard. Validez les signatures avant d’analyser les champs webhook comme fiables et rendez les opérations retentées idempotentes.
Gardez les effets de bord hors du rendu. Envoyer un e-mail, débiter une carte ou écrire des données analytiques dans le corps d’un composant peut se produire plusieurs fois. Effectuez ces actions dans une mutation, enregistrez assez d’état pour permettre les nouvelles tentatives sans risque et déplacez les tâches longues vers une file si la requête ne doit pas les attendre.
src/app/projects/actions.ts
"use server";
import { updateTag } from "next/cache";
export async function createProject(formData: FormData) {
const session = await verifySession();
if (!session) throw new Error("Unauthorized");
const input = projectSchema.parse({
name: formData.get("name"),
});
await db.project.create({ data: { ...input, ownerId: session.userId } });
updateTag("projects");
}10. Ajouter les métadonnées de recherche et de partage à chaque route
Le SEO commence par une page utile, explorable et une URL stable. Donnez à chaque route indexable un titre précis, une description claire, un H1 visible, des rubriques pertinentes, des liens descriptifs et un contenu qui répond complètement à la requête. Les métadonnées ne compensent pas un contenu trop maigre ni plusieurs URL qui publient le même contenu.
Exportez un objet metadata statique si les valeurs sont fixes. Utilisez generateMetadata si le titre, la description, l’URL canonique ou l’image dépend des données de la route. Définissez metadataBase une seule fois dans le layout racine pour résoudre correctement les URL relatives canoniques et des images sociales. Générez le sitemap et le fichier robots depuis la même source que les routes publiques, et excluez de l’indexation les URL privées ou en double.
Ajoutez Article, Product, BreadcrumbList ou un autre type JSON-LD approprié uniquement si le contenu visible de la page le justifie. Les données structurées doivent décrire ce que le lecteur voit réellement. Validez-les après le rendu et mettez dateModified à jour quand le contenu change sensiblement.
- —Utilisez app/robots.ts et app/sitemap.ts pour générer les fichiers destinés aux robots d’exploration.
- —Ajoutez des fichiers opengraph-image et twitter-image lorsqu’une route nécessite des visuels générés pour les réseaux sociaux.
- —Redirigez les anciennes URL et choisissez un hôte canonique, un protocole et une règle pour le slash final.
- —Reliez les pages associées avec des textes de lien descriptifs pour que les utilisateurs et les robots d’exploration puissent les découvrir.
Metadata for a dynamic article
import type { Metadata } from "next";
export async function generateMetadata({ params }): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
if (!post) return { title: "Article not found" };
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: "/blog/" + post.slug },
openGraph: {
type: "article",
title: post.title,
description: post.excerpt,
images: [post.ogImage],
},
};
}11. Préserver les performances au niveau des composants
Next.js fournit le découpage du code par route, les Server Components, le préchargement, l’optimisation d’images et des outils de polices, mais les choix applicatifs restent décisifs. Surveillez la limite côté client : une directive use client inclut ce module et ses imports clients dans le graphe du navigateur. Les grosses bibliothèques de graphiques, éditeurs, cartes et outils analytiques demandent des stratégies de chargement réfléchies.
Utilisez next/image avec les dimensions réelles ou un conteneur fill maîtrisé pour que le navigateur réserve l’espace nécessaire. Utilisez next/font pour héberger vous-même et précharger les fichiers de polices réellement utiles. Chargez les scripts tiers avec next/script et la stratégie la moins agressive qui satisfait le besoin métier.
Mesurez le comportement en production, pas seulement sur le serveur de développement. Utilisez Lighthouse comme test de laboratoire, collectez les Core Web Vitals lors des visites réelles, examinez les requêtes serveur lentes et analysez le bundle client lorsqu’une route régresse. Les budgets de performance sont plus utiles s’ils désignent une route et une métrique plutôt qu’un score unique pour tout le site.
A responsive image with reserved space
import Image from "next/image";
<Image
src="/product-dashboard.png"
alt="Northstar dashboard showing the weekly plan"
width={1600}
height={900}
sizes="(max-width: 768px) 100vw, 800px"
priority
/>12. Placer l’authentification près de l’accès aux données
L’authentification prouve l’identité ; l’autorisation détermine ce que cette identité peut faire. Utilisez une bibliothèque d’authentification maintenue, sauf si le produit a une raison solide de gérer lui-même les mots de passe, la rotation des sessions, la récupération de compte et l’intégration des prestataires. Stockez les données de session dans des cookies sécurisés HTTP-only et gardez les lectures sensibles sur le serveur.
Centralisez les contrôles d’autorisation sécurisés dans la couche d’accès aux données et vérifiez-les encore dans chaque Server Action et Route Handler. Proxy peut effectuer des redirections optimistes près des routes, mais ce n’est pas la seule protection : les utilisateurs peuvent appeler directement les endpoints publics de mutation, et le code serveur imbriqué a besoin de ses propres contrôles.
Seules les variables d’environnement préfixées par NEXT_PUBLIC_ ont leur place dans le code navigateur. Considérez leurs valeurs comme publiques dès la compilation. Marquez les modules de données privées avec server-only, renvoyez des DTO limités aux Client Components, validez chaque entrée et échappez ou nettoyez le contenu riche non fiable avant son rendu.
- —Utilisez des cookies de session sécurisés, HTTP-only et same-site lorsque la conception de l’authentification le permet.
- —Vérifiez la propriété ou le rôle à l'endroit de chaque lecture et écriture sensible.
- —Vérifiez les signatures webhook sur le corps brut de la requête lorsque le prestataire l’exige.
- —Ajoutez une politique de sécurité du contenu qui correspond aux scripts et aux ressources que le site charge vraiment.
- —Ne placez jamais de secrets, d’enregistrements privés de la base ni de détails d’erreur non filtrés dans les props d’un Client Component.
13. Tester le comportement au bon niveau
Testez les règles métier pures avec des tests unitaires sans faire intervenir Next.js. Testez par intégration la couche de données, les Server Actions et les Route Handlers avec des limites réalistes. Réservez les tests navigateur aux quelques parcours dont l’échec bloquerait l’utilisateur : connexion, création de la ressource principale, paiement ou publication.
L’accessibilité doit faire partie de l’implémentation et de la revue, sans se limiter à une analyse finale. Utilisez des éléments sémantiques, un focus clavier visible, des libellés associés aux champs, des messages d’erreur utiles, un contraste suffisant et un comportement adapté aux préférences de réduction des animations. Les contrôles automatisés ne couvrent qu’une partie des problèmes ; les vérifications au clavier et avec un lecteur d’écran révèlent ce que l’arbre des composants ne montre pas.
Intégrez la compilation de production à l’intégration continue. Dans Next.js 16, next build n’exécute plus le linter : lancez explicitement le lint, la vérification des types, les tests et la compilation de production. Démarrez l’application compilée et testez son contrôle de santé et ses routes critiques avant la promotion.
A straightforward CI verification sequence
npm ci
npm run lint
npx tsc --noEmit
npm test
npm run build
npm start14. Déployer le processus testé
Une application Next.js rendue sur le serveur a besoin d’un environnement Node.js compatible, du résultat de compilation, de variables d’environnement, d’une commande de démarrage et d’un contrôle de santé. Compilez depuis une copie propre du dépôt avec le fichier de verrouillage versionné. Gardez les valeurs privées dans le gestionnaire de secrets de la plateforme de déploiement, et non dans le dépôt ou l’image.
Une route de contrôle de santé doit indiquer si cette version peut recevoir du trafic. Gardez-la peu coûteuse et évitez de renvoyer des secrets ou des détails internes. Consultez séparément les journaux de compilation et d’exécution, vérifiez la route générée, puis associez le domaine personnalisé et le TLS géré. Si l’application dépend du disque local, de tâches en arrière-plan, de l’optimisation d’images ou de Cache Components, confirmez que l’hébergement prend en charge le comportement choisi.
Adios exécute le serveur de production Next.js habituel dans un processus Node.js persistant. Le manifeste de déploiement garde le contrat de compilation, de démarrage, de port, d’exécution et de santé auprès du code source : ces hypothèses peuvent ainsi être examinées avant une mise en production.
adios.yaml
name: northstar
region: de
replicas: 1
build_cmd: npm ci && npm run build
start_cmd: npm start
runtime:
name: node@24
port: 3000
health_path: /api/health
memory_mb: 1024
env:
DATABASE_URL: secret://DATABASE_URL
AUTH_SECRET: secret://AUTH_SECRET15. Utiliser une liste de vérification avant la mise en production
La revue finale doit relier le comportement du produit à celui de son exécution. Testez une compilation propre, une visite directe de chaque route critique, la navigation client entre layouts, une dépendance lente, une erreur de validation attendue, une exception non interceptée, un enregistrement manquant et une requête non autorisée. Consultez le code source HTML des pages publiques pour confirmer que les contenus et métadonnées importants sont présents sans attendre JavaScript côté client.
Testez ensuite la reprise. Arrêtez une dépendance requise, envoyez deux fois la même mutation, renouvelez un secret et déployez une version qui échoue au contrôle de santé. Un site est prêt lorsque l’équipe peut expliquer son démarrage, ses modes d’échec, la protection des utilisateurs pendant ces échecs et la façon dont la dernière version opérationnelle reste disponible.
- —Routes : URL canoniques, redirections, comportement 404, sitemap et règles robots corrects.
- —Rendu : les limites serveur/client sont choisies délibérément et les sections lentes affichent en streaming des contenus d’attente utiles.
- —Données : le cache, l’invalidation, les autorisations, les états vides et les erreurs correspondent aux besoins du produit.
- —Qualité : lint, types, tests, accessibilité, compilation de production et tests de bon fonctionnement réussis.
- —Exploitation : les secrets, contrôles de santé, journaux, domaine, TLS, retour arrière et responsabilités sont documentés.