Développement Web
Votre application web adaptée
à tous vos besoins
19/03/2025
Découvrez comment mettre en place un CMS moderne avec Payload et Next.js, de l'installation locale jusqu'au déploiement sécurisé sur un VPS avec Docker, Nginx et certificat SSL. Suivez notre tutoriel pas à pas pour maîtriser le déploiement, la gestion de contenu et l'automatisation du versionning avec Git.
Découvre comment mettre en place un CMS moderne avec Payload et Next.js, de l'installation locale jusqu'au déploiement sécurisé sur un VPS avec Docker, Nginx et certificat SSL. Suis notre tutoriel pas à pas pour maîtriser le déploiement, la gestion de contenu et l'automatisation du versionning avec Git.
1
brew install node
1
2
node -v
npm -v
(Exemple : MongoDB 8.0 – assure-toi d’adapter la commande selon la version souhaitée)
1
2
brew tap mongodb/brew
brew install mongodb-community@8.0
1
brew services start mongodb-community@8.0
1
brew services list
Mongo sera accessible sur mongodb://localhost:27017
(par défaut).
Documentation officielle :
Dans ton dossier de travail, exécute :
1
npx create-payload-app
Réponds aux questions :
blank
si tu veux un projet minimal)Tu peux utiliser Visual Studio Code, par exemple :
1
npm i
1
npm run dev
Objectif : Créer un type de contenu “Todo” (une simple liste de tâches) que tu pourras afficher sur ton site et administrer depuis Payload.
src/collections
.Todo.ts
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import type { CollectionConfig } from 'payload'
export const Todo: CollectionConfig = {
slug: 'todo',
admin: {
useAsTitle: 'Tâche',
},
fields: [
{
name: 'Tâche',
type: 'text',
required: true,
},
],
}
payload.config.ts
(dossier src
), ajoute la collection Todo
:/1
2
3
4
5
//avant:
collections: [Users, Media],
//après:
collections: [Users, Media, Todo],
Todo
en haut du fichier payload.config.ts
:1
import { Todo } from './collections/Todo'
Une fois le projet relancé, tu verras apparaître ta nouvelle collection “Todo” dans l’admin.
src/app/(frontend)/page.tsx
et modifie son contenu pour récupérer et afficher les tâches :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { getPayload } from 'payload'
import React from 'react'
import config from '@/payload.config'
import './styles.css'
export const dynamic = 'force-dynamic'
export default async function HomePage() {
const payload = await getPayload({ config });
const Todos = await payload.find({
collection: 'todo',
})
return (
<div>
{
Todos.docs?.map((todo) => (
<div key={todo.id}>
<h2>{todo.Tâche}</h2>
</div>
))
}
</div>
)
}
Maintenant, chaque tâche créée dans Payload s’affichera sur la page d’accueil.
1
ssh-keygen -t rsa -b 4096 -C "votre_email@example.com"
Entrée
pour accepter l'emplacement par défaut (~/.ssh/id_rsa
).Entrée
pour ignorer (optionnel mais recommandé)Lance l'agent SSH pour gérer la clé :
1
eval "$(ssh-agent -s)"
Ajoute la clé privée à l'agent pour qu'elle soit utilisée automatiquement :
1
ssh-add ~/.ssh/id_rsa
1
cat ~/.ssh/id_rsa.pub
Sélectionnez et copiez le contenu affiché.
1
ssh -T git@github.com
Un message de bienvenue devrait s’afficher.
1
2
3
git remote add origin git@github.com:beease/payload-test.git
git branch -M main
git push -u origin main
1
2
3
git add .
git commit -am "todo"
git push
Bravo, tes modifications sont maintenant en ligne, les autres développeurs ayant accès au projet pourront mettre leur version à jour avec tes changements avec la commande git pull
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
services:
payload:
build:
context: .
volumes:
- ./media:/app/media
ports:
- '3000:3000'
depends_on:
- mongo
mongo:
container_name: mongo
image: mongo:latest
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: username
MONGO_INITDB_ROOT_PASSWORD: password
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
nginx:
container_name: webserver
restart: always
image: nginx:latest
ports:
- '80:80'
volumes:
- ./nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
depends_on:
- payload
volumes:
mongodb_data:
Dockerfile
à la racine du projet :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build:compile
FROM node:22-alpine AS runner
WORKDIR /app
COPY . .
COPY --from=builder /app/package-lock.json ./
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
RUN npm ci --omit=dev
EXPOSE 3000
ENV PORT 3000
CMD ["sh", "-c", "npm run payload migrate && npm run build:generate && node server.js"]
(Ici, j’utilise Node 22 comme exemple. Adapte si besoin.)
next.config.mjs
active le mode standalone :1
2
3
4
5
6
7
8
import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
}
export default withPayload(nextConfig, { devBundleServerPackages: false })
package.json
à la racine, rajoute ces lignes de script :1
2
3
4
5
6
"scripts": {
...
"build:compile": "next build --experimental-build-mode compile",
"build:generate": "next build --experimental-build-mode generate",
...
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
upstream payload {
server payload:3000;
}
server {
listen 80;
listen [::]:80;
server_name [domain] www.[domain];
server_tokens off;
location / {
proxy_pass http://payload;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
1
2
3
git add .
git commit -am "setup docker & nginx"
git push
Sur le VPS, Docker se chargera d’installer tout ce dont tu as besoin à l’intérieur des conteneurs. Il te suffit d’installer Git et Docker sur la machine hôte.
1
2
3
sudo apt update
sudo apt install git -y
git --version
Suis la doc officielle de Docker :
1
2
3
4
5
6
7
8
9
10
11
12
13
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
1
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
1
sudo docker run hello-world
Même principe que sur ta machine locale (voir section C), afin de pouvoir cloner ton repo en SSH.
1
git clone git@github.com:MonCompte/mon-projet.git
.env
1
2
cd mon-projet
nano .env
1
2
DATABASE_URI=mongodb://username:password@mongo:27017/payload?authSource=admin
PAYLOAD_SECRET=[Clef secrète payload]
(Dans l’exemple : mongo correspond au nom du conteneur Mongo, défini dans docker-compose.yml.)
Pour sauvegarder et quitter Nano : Ctrl + O puis Ctrl + X (ou Cmd + S, Cmd + X sur Mac si SSH sous iTerm, etc.).
1
sudo docker compose build
1
sudo docker compose up
Le site est presque en ligne ! Manque plus qu’à configurer les DNS pour lier le nom de domaine à l’adresse physique du VPS`
Dans la configuration DNS de ton nom de domaine, ajoute un enregistrement de type A pointant vers l’adresse IP publique de ton VPS :
Par exemple :
Tu peux alors accéder à ton interface admin via : http://mon-domaine.fr/admin
docker-compose.yml
, ajoute (ou modifie) la config pour Nginx et Certbot :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
services:
nginx:
container_name: webserver
restart: always
image: nginx:latest
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
- ./certbot/www:/var/www/certbot/:ro
- ./certbot/conf/:/etc/nginx/ssl/:ro
depends_on:
- payload
entrypoint: "/bin/sh -c 'while :; do sleep 6h; nginx -s reload; done & nginx -g \"daemon off;\"'"
certbot:
restart: always
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
#entrypoint: "/bin/sh -c 'while :; do certbot renew; sleep 12h; done'"
nginx.conf
(version initiale pour la phase HTTP) :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
listen [::]:80;
server_name [domain].fr www.[domain].fr;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://[domain]$request_uri;
}
}
1
2
git pull
sudo docker compose down && sudo docker compose up
dry-run
: (remplace le nom de domaine par le tiens) :1
2
3
sudo docker compose run --rm certbot certonly \
--webroot --webroot-path /var/www/certbot/ \
--dry-run -d mon-domaine.fr
Le message "The dry run was successful" devrait s’afficher.
1
2
3
sudo docker compose run --rm certbot certonly \
--webroot --webroot-path /var/www/certbot/ \
-d mon-domaine.fr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
upstream payload {
server payload:3000;
}
# Redirection automatique HTTP -> HTTPS
server {
listen 80;
listen [::]:80;
server_name [domain].fr www.[domain].fr;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://[domain]$request_uri;
}
}
# Serveur HTTPS
server {
listen 443 default_server ssl http2;
listen [::]:443 ssl http2;
server_name [domain].fr www.[domain].fr;
ssl_certificate /etc/nginx/ssl/live/[domain.fr]/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/[domain.fr]/privkey.pem;
location / {
proxy_pass http://payload;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
docker-compose.yml
, décommente cette ligne pour renouveler automatiquement le certificat :1
2
3
4
5
6
7
8
certbot:
restart: always
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
==> entrypoint: "/bin/sh -c 'while :; do certbot renew; sleep 12h; done'"
1
2
git pull
sudo docker compose down && sudo docker compose up
Félicitations : ton application Payload est maintenant accessible en HTTPS via : https://[votre-domaine]/admin
Tu as maintenant un environnement complet pour :
Bonne continuation ! 🐝