Guide de Migration Monorepo pnpm - Next.js + Laravel
Objectif
Fusionner un projet Next.js (frontend) et Laravel (backend) dans un monorepo pnpm avec :
- Déploiement frontend via Vercel
- Déploiement backend via rsync + SSH (Hostinger ou autre)
- Workflows GitHub Actions séparés par dossier (
apps/web/**etapps/api/**)
Structure cible
mon-projet/
├── apps/
│ ├── web/ # Next.js (frontend)
│ │ ├── app/
│ │ ├── package.json # name: "mon-projet-web"
│ │ ├── next.config.js
│ │ └── ...
│ └── api/ # Laravel (backend)
│ ├── app/
│ ├── composer.json
│ ├── artisan
│ └── ...
├── package.json # Orchestrateur racine
├── pnpm-workspace.yaml
├── .npmrc
├── .github/
│ └── workflows/
│ ├── ci-web.yml # Déploiement Vercel
│ └── ci-api.yml # Déploiement rsync + SSH
└── .gitignore2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Étapes de migration
Étape 1 : Préparer le projet frontend existant
1.1 Créer la structure de dossiers
cd /chemin/vers/mon-projet-frontend
mkdir -p apps/web2
1.2 Déplacer les fichiers frontend dans apps/web/
Glisser-déposer tous les fichiers du frontend dans apps/web/, SAUF :
.git/(ne jamais toucher).github/(reste à la racine).gitmodules(si submodule, reste à la racine)node_modules/(sera régénéré).next/(sera régénéré)
1.3 Mettre à jour le nom du package
Dans apps/web/package.json, changer le nom :
{
"name": "mon-projet-web",
...
}2
3
4
Étape 2 : Créer les fichiers de configuration racine
2.1 package.json (racine)
{
"name": "mon-projet",
"private": true,
"scripts": {
"dev": "pnpm dev:web",
"dev:web": "pnpm --filter mon-projet-web dev",
"dev:api": "cd apps/api && php artisan serve --host=0.0.0.0 --port=8000",
"build": "pnpm build:web",
"build:web": "pnpm --filter mon-projet-web build",
"lint": "pnpm lint:web",
"lint:web": "pnpm --filter mon-projet-web lint",
"test": "pnpm test:web",
"test:web": "pnpm --filter mon-projet-web test"
},
"packageManager": "pnpm@9.0.0"
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.2 pnpm-workspace.yaml (racine)
packages:
- apps/web
# Si tu as un submodule design system :
# - apps/web/app/components/mon-designsystem
onlyBuiltDependencies:
- "@sentry/cli"
- sharp2
3
4
5
6
7
8
2.3 .npmrc (racine)
shamefully-hoist=true
public-hoist-pattern[]=*2
Étape 3 : Ajouter le backend Laravel
Option A : Git Subtree (conserve l'historique)
cd /chemin/vers/mon-projet
git subtree add --prefix=apps/api https://github.com/mon-org/mon-projet-backend.git main --squash2
Option B : Copie simple (sans historique)
cp -r /chemin/vers/backend/* apps/api/3.1 Supprimer le .github du backend
Le dossier .github importé avec le backend doit être supprimé (les workflows sont à la racine) :
rm -rf apps/api/.githubÉtape 4 : Configurer les GitHub Actions
4.1 Workflow Frontend : .github/workflows/ci-web.yml
name: "[MonProjet] Web - Deploy"
on:
push:
branches:
- main
- dev
paths:
- "apps/web/**"
- "pnpm-workspace.yaml"
- "package.json"
pull_request:
branches:
- main
- dev
paths:
- "apps/web/**"
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/web
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Si tu as un submodule :
# - name: Update git submodules
# run: |
# cd ../..
# git submodule update --init --recursive
- name: Set environment variables
run: |
echo "NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}" >> $GITHUB_ENV
echo "NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }}" >> $GITHUB_ENV
# Ajouter autres variables...
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
cache-dependency-path: apps/web/pnpm-lock.yaml
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Deploy to Vercel
if: github.event_name == 'push'
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
working-directory: ./apps/web
scope: mon-org
vercel-args: ${{ github.ref == 'refs/heads/main' && '--prod' || '' }}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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
4.2 Workflow Backend : .github/workflows/ci-api.yml
name: "[MonProjet] API - Deploy"
on:
push:
branches:
- dev
- main
paths:
- "apps/api/**"
jobs:
deploy-dev:
name: "Deploy DEV"
if: github.ref == 'refs/heads/dev'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy via rsync
uses: burnett01/rsync-deployments@6.0.0
with:
switches: -avzr --delete --exclude='.env' --exclude='vendor' --exclude='storage/logs/*' --exclude='storage/framework/cache/*' --exclude='storage/framework/sessions/*' --exclude='storage/framework/views/*' --exclude='.git'
path: apps/api/
remote_path: ~/chemin/vers/backend-dev/
remote_host: ${{ secrets.SSH_HOST }}
remote_port: ${{ secrets.SSH_PORT }}
remote_user: ${{ secrets.SSH_USER }}
remote_key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Run Laravel commands
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ~/chemin/vers/backend-dev
echo "${{ secrets.ENV_DEV_BASE64 }}" | base64 -d > .env
~/composer2 install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:clear
php artisan route:clear
php artisan cache:clear
deploy-prod:
name: "Deploy PROD"
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy via rsync
uses: burnett01/rsync-deployments@6.0.0
with:
switches: -avzr --delete --exclude='.env' --exclude='vendor' --exclude='storage/logs/*' --exclude='storage/framework/cache/*' --exclude='storage/framework/sessions/*' --exclude='storage/framework/views/*' --exclude='.git'
path: apps/api/
remote_path: ~/chemin/vers/backend-prod/
remote_host: ${{ secrets.SSH_HOST }}
remote_port: ${{ secrets.SSH_PORT }}
remote_user: ${{ secrets.SSH_USER }}
remote_key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Run Laravel commands
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ~/chemin/vers/backend-prod
echo "${{ secrets.ENV_PROD_BASE64 }}" | base64 -d > .env
~/composer2 install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:clear
php artisan route:clear
php artisan cache:clear2
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
Étape 5 : Configurer Vercel
- Aller dans Settings → General
- Root Directory :
apps/web - Include files outside the root directory in the Build Step : ✅ Activé
Étape 6 : Configurer les secrets GitHub
Aller dans Settings → Secrets and variables → Actions et ajouter :
Frontend (Vercel)
| Secret | Description |
|---|---|
VERCEL_TOKEN | Token Vercel |
VERCEL_ORG_ID | ID de l'organisation Vercel |
VERCEL_PROJECT_ID | ID du projet Vercel |
NEXT_PUBLIC_API_URL | URL de l'API |
NEXTAUTH_SECRET | Secret NextAuth |
Backend (SSH/rsync)
| Secret | Description |
|---|---|
SSH_HOST | Adresse IP du serveur |
SSH_PORT | Port SSH (souvent 22 ou 65002) |
SSH_USER | Utilisateur SSH |
SSH_PRIVATE_KEY | Clé privée SSH |
ENV_DEV_BASE64 | Fichier .env encodé en base64 pour DEV |
ENV_PROD_BASE64 | Fichier .env encodé en base64 pour PROD |
Encoder un fichier .env en base64
cat .env | base64 -w 0
# ou sur macOS :
cat .env | base642
3
Étape 7 : Préparer le serveur (première fois)
En SSH sur le serveur, supprimer le lien git existant si présent :
cd ~/chemin/vers/backend
rm -rf .git2
Cela garantit que seul rsync est utilisé pour le déploiement.
Étape 8 : Commit et push
git add .
git commit -m "chore: migrate to pnpm monorepo"
git push origin dev2
3
Fonctionnement des déploiements
| Action | Workflow déclenché |
|---|---|
Push dans apps/web/** | ci-web.yml → Vercel |
Push dans apps/api/** | ci-api.yml → rsync + SSH |
| Push dans les deux | Les deux workflows |
| Push ailleurs (racine, docs, etc.) | Aucun workflow |
Commandes utiles
Développement local
# Installer les dépendances (depuis la racine)
pnpm install
# Lancer le frontend
pnpm dev:web
# Lancer le backend
pnpm dev:api
# Build frontend
pnpm build:web2
3
4
5
6
7
8
9
10
11
Git Submodule (si design system)
# Mettre à jour le submodule
cd apps/web/app/components/mon-designsystem
git pull origin main
cd ../../../../..
git add apps/web/app/components/mon-designsystem
git commit -m "chore: update design system"2
3
4
5
6
Git Subtree (synchroniser le backend si besoin)
# Tirer les mises à jour du repo backend original
git subtree pull --prefix=apps/api https://github.com/mon-org/backend.git main --squash
# Pousser vers le repo backend original (optionnel)
git subtree push --prefix=apps/api https://github.com/mon-org/backend.git main2
3
4
5
Vérification sur le serveur
# Vérifier les fichiers récemment modifiés
ls -lt | head -10
# Vérifier Laravel
php artisan --version
php artisan migrate:status
# Vérifier qu'il n'y a plus de .git
ls -la .git # Doit retourner "No such file or directory"2
3
4
5
6
7
8
9
Checklist de migration
- [ ] Créer
apps/web/et déplacer le frontend - [ ] Renommer le package dans
apps/web/package.json - [ ] Créer
package.jsonracine - [ ] Créer
pnpm-workspace.yaml - [ ] Créer
.npmrc - [ ] Ajouter le backend dans
apps/api/(subtree ou copie) - [ ] Supprimer
apps/api/.github/si présent - [ ] Créer
.github/workflows/ci-web.yml - [ ] Créer
.github/workflows/ci-api.yml - [ ] Configurer Vercel (Root Directory + Include files)
- [ ] Ajouter les secrets GitHub
- [ ] Supprimer
.gitsur le serveur (SSH) - [ ] Tester
pnpm installlocalement - [ ] Push sur
devet vérifier les workflows - [ ] Push sur
mainet vérifier en production - [ ] Archiver l'ancien repo backend sur GitHub
Notes importantes
Exclusions rsync
rsync exclut automatiquement : .env, vendor/, storage/logs/*, .git
Fichier .env
Le .env est recréé à chaque déploiement via le secret ENV_*_BASE64
Workflows conditionnels
Les workflows ne se déclenchent que si les fichiers correspondants changent (paths filter)
Configuration Vercel
Vercel doit avoir "Include files outside root directory" activé