ITSkillsCenter
Développement Web

Installer Payload CMS 3 dans une application Next.js — pas à pas 2026

13 دقائق للقراءة

Payload CMS 3 est le premier CMS qui s’installe directement à l’intérieur d’une application Next.js, sous le dossier /app. À la différence de Strapi ou Directus qui tournent comme des services séparés, Payload partage le même processus, le même runtime et le même panneau de build que votre front-end. Pour une équipe Next.js, cela supprime une grosse part de la complexité du déploiement : une seule application à conteneuriser, une seule pipeline CI, une seule URL.

Ce tutoriel installe Payload 3.84+ dans une application Next.js 15 fraîche, avec PostgreSQL en base de données et TypeScript activé. À la fin, vous aurez un panneau d’administration accessible sous /admin, une collection « Articles » fonctionnelle, et une API REST consommable depuis votre front-end. Comptez 30 à 45 minutes la première fois.

Prérequis

  • Node.js 20 LTS ou 22 LTS installé (Payload exige une LTS active ; vérifier avec node --version)
  • npx ou pnpx disponible (livré avec npm 10+ ou pnpm 9+)
  • Une base PostgreSQL accessible — locale via Docker ou hébergée (Neon, Supabase, Railway)
  • Un éditeur compatible TypeScript (VS Code recommandé)
  • Connaissance de base de Next.js App Router (routes, layouts, server components)

Pour la base PostgreSQL, le plus simple en local est de lancer un conteneur Docker éphémère. Si Docker n’est pas installé, l’alternative gratuite la plus rapide est Neon : 100 Mo de stockage offerts, pas de carte requise, branche éphémère pour le développement. Cette deuxième voie convient parfaitement pour suivre ce tutoriel.

Étape 1 — Créer le projet avec create-payload-app

Payload fournit un assistant de création de projet qui scaffolde une application Next.js 15 préconfigurée avec Payload, TypeScript et l’adaptateur de base choisi. C’est la voie la plus rapide pour partir d’un terrain propre : aucun risque d’incompatibilité de version, configuration générée correctement du premier coup. Lancez la commande suivante dans le dossier où vous voulez créer le projet (par exemple ~/projets).

npx create-payload-app@latest

L’assistant pose quatre questions. Première question, le nom du projet : tapez mon-blog-payload. Deuxième question, le template : choisissez blank pour partir vierge, ou website pour un blog d’exemple complet (recommandé pour la première fois). Troisième question, l’adaptateur de base : sélectionnez postgres. Quatrième question, la chaîne de connexion : laissez vide pour le moment, on la remplira ensuite. L’installation prend deux à trois minutes selon la connexion ; à la fin, le terminal affiche les instructions pour démarrer.

Étape 2 — Démarrer une base PostgreSQL locale

Payload a besoin d’une base PostgreSQL pour stocker les contenus, les utilisateurs et les fichiers métadonnées. Pour un usage de développement, un conteneur Docker éphémère fait l’affaire en trois lignes : pas de configuration système, pas de service à démarrer au boot, on supprime tout après l’apprentissage. Le port 5432 par défaut de PostgreSQL est exposé sur l’hôte, ce qui permet à Payload de s’y connecter via localhost.

docker run --name payload-pg \
  -e POSTGRES_PASSWORD=devpass \
  -e POSTGRES_DB=payload \
  -p 5432:5432 \
  -d postgres:18-alpine

Cette commande télécharge l’image PostgreSQL 18 (la version stable depuis février 2026) en variante Alpine pour gagner en taille, démarre un conteneur nommé payload-pg, expose le port 5432, et configure un mot de passe et une base nommée payload. Vérifiez que tout tourne avec docker ps : vous devez voir une ligne avec payload-pg et le statut Up. Si Docker n’est pas disponible, créez une base sur Neon, copiez la chaîne de connexion fournie, et passez directement à l’étape suivante.

Étape 3 — Configurer la chaîne de connexion

Payload lit ses paramètres sensibles depuis un fichier .env à la racine du projet. Cette pratique évite de commiter par accident des secrets dans Git, et permet de basculer facilement entre environnements (dev, staging, production). L’assistant a déjà créé le fichier ; il faut maintenant y inscrire l’URL de la base PostgreSQL.

# .env
DATABASE_URI=postgres://postgres:devpass@localhost:5432/payload
PAYLOAD_SECRET=remplacez-par-une-chaine-aleatoire-de-32-caracteres
NEXT_PUBLIC_SERVER_URL=http://localhost:3000

La variable DATABASE_URI suit le format standard PostgreSQL postgres://user:password@host:port/database. La variable PAYLOAD_SECRET sert à signer les jetons d’authentification — générez une chaîne aléatoire avec openssl rand -hex 32 ou node -e "console.log(crypto.randomBytes(32).toString('hex'))". Sans secret robuste, le panneau d’administration est exploitable. La variable NEXT_PUBLIC_SERVER_URL indique à Payload comment se référer à lui-même pour les liens absolus.

Étape 4 — Lancer l’application en mode développement

Tout est en place pour le premier démarrage. Payload va se connecter à PostgreSQL, créer automatiquement le schéma de la base (tables users, media, plus celles déduites des collections), puis exposer le panneau d’administration. Le premier lancement prend une dizaine de secondes ; les suivants sont instantanés.

cd mon-blog-payload
npm run dev

La console affiche les journaux de démarrage : connexion PostgreSQL OK, migration appliquée, serveur Next.js prêt sur http://localhost:3000. Ouvrez ce port dans le navigateur. Le front-end public est presque vide pour le moment ; ce qui nous intéresse est http://localhost:3000/admin. Une page de création du premier compte administrateur s’affiche : renseignez votre adresse e-mail et un mot de passe robuste de 12 caractères au minimum, puis validez. Vous arrivez sur le tableau de bord Payload.

Étape 5 — Créer une collection « Articles »

À la différence de Strapi qui propose un Content-Type Builder graphique, Payload se configure entièrement en TypeScript. Cette approche déclarative offre plusieurs avantages : la configuration vit dans Git, elle est revue en pull request, et elle est typée automatiquement côté front-end. Le fichier de configuration principal est src/payload.config.ts, et chaque collection est généralement définie dans un fichier dédié sous src/collections/.

Créez le fichier src/collections/Articles.ts avec le contenu suivant :

import type { CollectionConfig } from 'payload'

export const Articles: CollectionConfig = {
  slug: 'articles',
  admin: {
    useAsTitle: 'titre',
    defaultColumns: ['titre', 'auteur', 'updatedAt'],
  },
  access: {
    read: () => true,
  },
  fields: [
    {
      name: 'titre',
      type: 'text',
      required: true,
    },
    {
      name: 'slug',
      type: 'text',
      required: true,
      unique: true,
    },
    {
      name: 'extrait',
      type: 'textarea',
      maxLength: 280,
    },
    {
      name: 'corps',
      type: 'richText',
      required: true,
    },
    {
      name: 'auteur',
      type: 'relationship',
      relationTo: 'users',
      required: true,
    },
    {
      name: 'publishedAt',
      type: 'date',
    },
  ],
}

Cette définition crée une collection articles avec six champs : titre obligatoire, slug unique pour les URLs publiques, extrait court limité à 280 caractères, corps riche éditable via Lexical (l’éditeur par défaut de Payload), relation vers la collection users pour l’auteur, et date de publication. Le bloc access.read rend les articles lisibles par tous, ce qui est ce que l’on attend d’un site public ; les autres opérations (create, update, delete) restent réservées aux utilisateurs connectés par défaut.

Reste à enregistrer la collection dans la configuration principale. Ouvrez src/payload.config.ts, importez la collection en haut du fichier avec import { Articles } from './collections/Articles', et ajoutez Articles dans le tableau collections. Sauvegardez. Le serveur Next.js redémarre automatiquement, applique la migration sur la base PostgreSQL pour créer la table articles avec ses colonnes, et le panneau d’administration affiche un nouvel item « Articles » dans le menu de gauche.

Étape 6 — Créer un premier article depuis le panneau

Cliquez sur « Articles » dans le menu, puis sur le bouton « Create New ». Le formulaire affiche les six champs définis à l’étape précédente. Renseignez un titre (« Premier article test »), un slug (« premier-article-test »), un extrait (deux à trois phrases), et écrivez quelques paragraphes dans le corps. Sélectionnez votre utilisateur dans le champ auteur. Renseignez la date de publication. Cliquez sur « Save ».

Si tout s’est bien passé, l’article apparaît dans la liste avec un identifiant unique, une date de création et de dernière modification. C’est le moment de vérifier que la migration PostgreSQL a fonctionné : ouvrez un terminal, connectez-vous au conteneur Docker avec docker exec -it payload-pg psql -U postgres -d payload, puis exécutez \dt. Vous devez voir une dizaine de tables, dont articles, users, media, payload_migrations. Un SELECT id, titre FROM articles; renvoie la ligne que vous venez de créer. Tapez \q pour quitter.

Étape 7 — Consommer l’API REST depuis le front-end

Payload expose automatiquement une API REST sous /api/articles dès que la collection est enregistrée. Cette API suit les conventions classiques : GET /api/articles liste tous les articles, GET /api/articles/:id renvoie un article précis, POST, PATCH, DELETE permettent les écritures pour les utilisateurs autorisés. Mais Payload offre aussi une « Local API » qui contourne complètement HTTP : dans un Server Component Next.js, on appelle Payload directement via une fonction TypeScript, ce qui économise un aller-retour réseau et profite des types complets.

Modifiez la page d’accueil src/app/(frontend)/page.tsx (ou créez-la si elle n’existe pas) avec le code suivant :

import { getPayload } from 'payload'
import config from '@payload-config'

export default async function Home() {
  const payload = await getPayload({ config })
  const result = await payload.find({
    collection: 'articles',
    limit: 10,
    sort: '-publishedAt',
  })

  return (
    <main>
      <h1>Derniers articles</h1>
      <ul>
        {result.docs.map((article) => (
          <li key={article.id}>
            <a href={`/articles/${article.slug}`}>{article.titre}</a>
          </li>
        ))}
      </ul>
    </main>
  )
}

Le composant est un Server Component asynchrone. Au moment du rendu côté serveur, il récupère les dix derniers articles directement depuis Payload via payload.find(), sans HTTP. La réponse contient un tableau docs avec les articles complets, plus des métadonnées de pagination (totalDocs, page, hasNextPage, etc.). Sauvegardez, rechargez http://localhost:3000 : la liste affiche votre article test. Si vous obtenez une erreur sur l’import @payload-config, vérifiez que le fichier tsconfig.json contient bien l’alias dans la section paths — l’assistant l’ajoute par défaut, mais une suppression accidentelle peut le casser.

Étape 8 — Activer l’upload de médias

La gestion des images et fichiers se fait via une collection spéciale media que Payload pré-configure pour stocker les fichiers sur le disque local sous media/. Ce stockage convient au développement mais devient inadapté en production : chaque déploiement perdrait les uploads précédents si le conteneur est immuable. La pratique courante consiste à brancher Payload sur un stockage objet S3-compatible (Hetzner Object Storage, MinIO, Cloudflare R2).

Pour le développement local, gardez le stockage disque par défaut. Ouvrez src/collections/Media.ts (généré par l’assistant) et vérifiez la présence du bloc upload: { staticDir: 'media' }. Pour ajouter une image, allez dans « Media » dans le panneau et cliquez sur « Upload ». Le fichier est sauvegardé sous media/ et référençable depuis n’importe quelle collection via un champ de type upload.

Pour brancher S3 plus tard en production, installez l’adaptateur officiel avec npm install @payloadcms/storage-s3, configurez les clés AWS-compatibles dans le .env, et remplacez le bloc upload dans la configuration. La documentation officielle détaille la syntaxe exacte ; comptez vingt minutes pour la configuration la première fois.

Étape 9 — Vérification finale et nettoyage

À ce stade, l’application doit présenter trois choses fonctionnelles : un panneau d’administration sous /admin où l’on peut créer et éditer des articles, une page d’accueil sous / qui liste les articles publiés, et une API REST sous /api/articles qui renvoie le contenu en JSON. Testez les trois rapidement pour vous assurer que tout est cohérent.

curl http://localhost:3000/api/articles | head -c 500

La commande renvoie un JSON contenant un tableau docs avec vos articles, plus les méta de pagination. Si la réponse est {"errors":[...]}, vérifiez les permissions de la collection (le bloc access.read). Si la réponse est vide, c’est que vous n’avez pas encore créé d’article — retournez à l’étape 6. Si la réponse renvoie une erreur 500, lisez le terminal du serveur de développement : l’origine du problème vient presque toujours d’une chaîne de connexion PostgreSQL incorrecte ou d’un secret manquant.

Pour arrêter proprement l’environnement, faites Ctrl+C dans le terminal Next.js, puis stoppez et supprimez le conteneur PostgreSQL avec docker stop payload-pg && docker rm payload-pg. Si vous avez utilisé Neon, aucun nettoyage n’est nécessaire : la branche éphémère se met en pause automatiquement après quelques minutes d’inactivité.

Erreurs fréquentes et solutions

ErreurCauseSolution
ECONNREFUSED 127.0.0.1:5432PostgreSQL pas démarré ou mauvais portVérifier docker ps ; relancer le conteneur si arrêté
password authentication failedMot de passe différent dans .env et conteneurSynchroniser POSTGRES_PASSWORD Docker et DATABASE_URI
Module not found: '@payload-config'Alias TypeScript manquantAjouter dans tsconfig.json sous compilerOptions.paths
PAYLOAD_SECRET is requiredSecret vide dans .envGénérer 32 octets avec openssl rand -hex 32
Panneau admin se charge mais ne se connecte pasCookies bloqués par navigateur strictTester en navigation privée ou autoriser localhost

Aller plus loin

Quatre directions naturelles pour étoffer ce projet : ajouter une page de détail d’article avec le rendu Lexical en HTML via le composant RichText de Payload, configurer un stockage S3 pour la production, mettre en place l’authentification publique pour permettre des commentaires, et déployer le tout sur un VPS Hetzner avec Docker Compose. Les tutoriels associés couvrent ces sujets, en particulier le déploiement Strapi sur Hetzner dont la logique transpose à 80 % sur Payload.

Pour la mise en production, deux décisions structurantes méritent un temps de réflexion. La première est le choix de la base : PostgreSQL pour les projets relationnels classiques avec des contraintes de schéma fortes, MongoDB pour les documents très hétérogènes ou les besoins de scaling horizontal. La seconde est le choix d’hébergement : un seul VPS Hetzner CCX13 pour les projets jusqu’à quelques dizaines de milliers de visites par mois, séparation base/application sur deux VPS au-delà, ou hybride Vercel pour le front + Hetzner pour la base si vous voulez profiter du CDN edge sans vendor-lock complet.

Tutoriels associés

Ressources officielles

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité