Next.js en production
À utiliser pour tout code dans une application Next.js 16 App Router. Les règles spécifiques à la sécurité (authentification dans les actions, CSP, en-têtes, taint) se trouvent dans nextjs-security ; les règles de conteneur dans docker-nextjs. Le skill intégré next-best-practices de Claude Code couvre en profondeur les conventions générales de fichiers, les métadonnées, l'optimisation des images et des polices ; ce skill n'ajoute que ce qui est spécifique à notre façon de construire et de livrer.
Base de référence : Next 16.4, React 19.3, Turbopack (par défaut pour dev et build), cacheComponents: true avec partialPrefetching: true, output: "standalone", typedRoutes: true. Le next.config.ts du modèle est la source de vérité.
Règles
-
Composants serveur par défaut ;
"use client"uniquement pour l'état, les effets, les API navigateur ou les gestionnaires d'événements. Placez la directive sur la plus petite feuille qui en a besoin. Ne l'ajoutez pas parce qu'un fichier importe nuvui : les parties avec état de nuvui portent leur propre directive (voir le skillnuvui,rules/server-components.md).// Erreur : toute la page est client à cause d'un seul gestionnaire "use client"; export default function Page() { … <Select onValueChange={…} /> … } // Correct : la page reste sur le serveur, le gestionnaire vit dans une feuille export default async function Page() { const people = await getPeople(); return <PeopleFilter people={people} />; // people-filter.tsx a "use client" }Les props passées à un composant client doivent être sérialisables : pas de fonctions (sauf les actions serveur), pas d'instances de classe.
-
Récupérez les données sur le serveur, dans le composant qui en a besoin. Pas de récupération avec
useEffect, pas de gestionnaire de route uniquement pour alimenter votre propre page. Dédupliquez par requête aveccache()de React ; mettez en cache entre requêtes avec la règle 3. -
Mettez en cache avec
"use cache"+cacheLife+cacheTag; tout le reste est dynamique. Avec les Composants Cache, rien n'est mis en cache sauf si vous le dites, etfetchn'a pas de cache implicite.import { cacheLife, cacheTag } from "next/cache"; export async function getInvoice(id: InvoiceId) { "use cache"; cacheLife("minutes"); cacheTag(`invoice:${id}`); return db.invoice.findUnique({ where: { id } }); }N'utilisez pas l'ancienne configuration de segment (
export const dynamic,revalidate,fetchCache) : elle génère une erreur avec les Composants Cache. Les arguments et les valeurs fermées deviennent partie de la clé de cache, donc passez des ids, pas des objets de requête entiers. Détails, les profils et"use cache: private": references/caching.md. -
Invalidez avec
updateTagdans les actions serveur,revalidateTag(tag, "max")ailleurs.updateTag(tag)permet les lectures après écriture et ne fonctionne qu'à l'intérieur d'une action serveur. Depuis un webhook ou un gestionnaire de route, utilisezrevalidateTag(tag, "max")(stale-while-revalidate) ourevalidateTag(tag, { expire: 0 }). La version à un argumentrevalidateTag(tag)est dépréciée. -
Enveloppez les données au moment de la requête dans
<Suspense>, sinon la build échoue.cookies(),headers(),searchParams,connection()et les E/S non mises en cache en dehors d'une limite Suspense arrêtent le pré-rendu avec « uncached or runtime data during prerendering ». Donnez-lui une limite avec un squelette de fallback, mettez-le en cache, ou (layout racine uniquement, voir règle 14) marquez le blocage de la route avecexport const instant = false. Tous lesparams,searchParams,cookies(),headers()sont asynchrones : attendez-les avecawait. -
Chaque action serveur valide ses entrées et vérifie elle-même l'autorisation. Une action serveur est un point de terminaison POST public, quelle que soit la page sur laquelle elle est écrite. Analysez
FormDataavec le schéma detypescript-production(valibot), puis autorisez, puis muterez, puisupdateTag. Retournez un résultat typé pour les échecs attendus ; ne levez une exception que pour les bugs. L'ensemble complet des règles se trouve dansnextjs-security."use server"; export async function renameProject(_: State, form: FormData): Promise<State> { const session = await requireSession(); // 401 si absent const input = v.safeParse(RenameProject, Object.fromEntries(form)); if (!input.success) return { ok: false, errors: flatten(input.issues) }; await assertCanEdit(session, input.output.projectId); // 403 si non autorisé await db.project.update(…); updateTag(`project:${input.output.projectId}`); return { ok: true }; } -
Les gestionnaires de route sont destinés aux autres clients (webhooks, mobile, santé), pas à vos propres pages. Placez-les sous
app/api/. Validez le corps comme une action, retournezResponse.json(...), et définissez les codes de statut intentionnellement. -
Chaque application a
GET /api/health, dynamique et sans dépendance. LeHEALTHCHECKDocker et les sondes Kubernetes l'appellent. Il doit répondre sans toucher à la base de données, afin qu'une dépendance lente ne redémarre pas chaque pod.await connection()l'empêche d'être pré-rendu en un fichier statique. Il se trouve dans le modèle àsrc/app/api/health/route.ts;proxy.tsexclut/api/healthde son filtre. -
error.tsx,loading.tsxetnot-found.tsxà la racine, et par segment là où l'expérience utilisateur diffère.error.tsxest un composant client ; enregistrez viaonRequestErrordeinstrumentation.ts, affichez une réessai avecreset, jamais le message d'erreur. AppeleznotFound()pour les enregistrements manquants au lieu de rendre un état vide. -
NEXT_PUBLIC_*est intégré à la build ; tout le reste est lu à l'exécution sur le serveur. Une seule image est promue de la branche principale à la production inchangée, donc une valeurNEXT_PUBLIC_est la même dans tous les environnements. Mettez-y uniquement des constantes de build (nom de l'application, un DSN Sentry public identique partout). La configuration par environnement (URLs API, feature flags, secrets) est unprocess.env.Xcôté serveur, lu dans un composant serveur ou une action, et passé au client sous forme de props si nécessaire. Validez l'environnement une fois danssrc/env.ts(valibot) et importez de là. N'utilisez jamais la cléenvdansnext.config.ts: elle est incorporée dans les bundles clients. -
Marquez les modules serveur uniquement avec
import "server-only". Tout ce qui lit des secrets, parle à la base de données ou importe un SDK avec des identifiants. Une importation cliente de ce module échoue alors à la build au lieu de fuiter. -
after()pour le travail qui ne doit pas retarder la réponse (journaux d'audit, analytics, préchauffage du cache). Lisezcookies()/headers()avant d'appelerafter, pas à l'intérieur. Il s'exécute dans le même processus, donc le pod a besoin de son délai de grâce SIGTERM (le chart définitterminationGracePeriodSeconds). -
Instrumentez avec
src/instrumentation.ts:registerOTel+onRequestError. OpenTelemetry via@vercel/otel, exportateur configuré par les variables d'environnement standardOTEL_EXPORTER_OTLP_*à l'exécution. Voir references/observability.md. -
Gardez
output: "standalone"et n'importez jamais depuis l'extérieur du workspace. Le runner Docker copie uniquement.next/standalone,.next/staticetpublic. Dans le monorepo,server.jsatterrit à.next/standalone/apps/<app>/server.js; la racine du workspace est trouvée à partir depnpm-lock.yaml, donc ne définissez pasoutputFileTracingRootouturbopack.rootsauf si la build prévient. Détails de l'auto-hébergement (multi-réplicas, clés d'actions serveur,deploymentId) : references/self-hosting.md. -
Le layout racine lit le nonce CSP, donc l'application s'affiche par requête. Le
proxy.tsdu modèle définit un CSP basé sur un nonce etsrc/app/layout.tsxaexport const instant = false. C'est un compromis délibéré : les nonces et les shells statiques pré-rendus ne peuvent pas coexister, et pour les applications métier connectées, un CSP strict l'emporte. La mise en cache des données avec"use cache"fonctionne toujours. Ne supprimez aucune des deux parties sans lire la règle 4 denextjs-security. -
Styles : SCSS BEM global, importé par le composant. Turbopack compile Sass avec le package
sass;sassOptions.loadPaths: ["src/styles"]rend@use "abstracts"accessible depuis n'importe quelle profondeur. Pas d'imports~, pas desassOptions.functions. Règles dansscss-bem. -
Images via
next/image, polices vianext/font.sharpest fourni avec Next pour l'optimiseur ; l'image du runner n'a besoin d'aucun package supplémentaire. Les images distantes nécessitentimages.remotePatternsavec un nom d'hôte exact. -
Vérifiez la taille du bundle avec
pnpm next analyze(l'analyseur de Turbopack ;@next/bundle-analyzerest réservé à webpack). Chargez paresseusement les widgets clients lourds (éditeur nuvui, cartes, graphiques sous la ligne de flottaison) avecnext/dynamic. -
Utilisez les routes typées et les types générés.
typedRoutes: truevérifie<Link href>;PageProps<"/invoices/[id]">etLayoutPropssont globaux aprèsnext typegen(exécuté automatiquement parnext dev/next build). N'écrivez pas manuellement les typesparams. -
Pas de
next lint. Il a été supprimé dans la version 16. Biome se charge du lint (voirbiome) ; TypeScript vérifie avectsc --noEmitpar package.
Vérifier
pnpm typecheck
pnpm lint
pnpm build # turbo run build : échoue en cas d'erreurs de pré-rendu, d'erreurs de type, d'erreurs Sass
pnpm --filter <app> start & # puis :
curl -fsS localhost:3000/api/health
Sources
- https://nextjs.org/docs/app/guides/upgrading/version-16
- https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents
- https://nextjs.org/docs/app/guides/migrating-to-cache-components
- https://nextjs.org/docs/app/api-reference/directives/use-cache
- https://nextjs.org/docs/app/api-reference/functions/revalidateTag
- https://nextjs.org/docs/app/api-reference/functions/updateTag
- https://nextjs.org/docs/messages/blocking-prerender-dynamic
- https://nextjs.org/docs/app/api-reference/functions/after
- https://nextjs.org/docs/app/guides/open-telemetry
- https://nextjs.org/docs/app/api-reference/config/next-config-js/output
- https://nextjs.org/docs/app/guides/self-hosting
- https://nextjs.org/docs/app/guides/environment-variables
- https://nextjs.org/docs/app/guides/sass
- https://nextjs.org/docs/app/guides/package-bundling
- https://nextjs.org/docs/app/api-reference/file-conventions/proxy