Cours Next.js · Module 8: Déploiement et production

CI/CD et automatisation du déploiement

14 min de lecture
Dans cette leçon12

Dans le développement logiciel moderne, l'intégration continue (Continuous Integration, CI) et le déploiement continu (Continuous Deployment, CD) sont des pratiques clés qui permettent de livrer les applications plus rapidement et de façon plus fiable. Next.js, en tant que framework moderne, s'intègre parfaitement avec divers outils CI/CD, ce qui permet d'automatiser le processus de test, de build et de déploiement des applications.

Qu'est-ce que le CI/CD ?

Intégration continue (CI)

L'intégration continue est une pratique où les développeurs intègrent régulièrement leurs modifications de code dans la branche principale du dépôt. Chaque intégration est automatiquement vérifiée par :

  • L'exécution des tests unitaires et d'intégration
  • L'analyse statique du code (linting)
  • La vérification de la qualité du code
  • La vérification des types (dans le cas de TypeScript)

La CI permet de détecter tôt les erreurs et garantit que le code dans la branche principale reste stable.

Déploiement continu (CD)

Le déploiement continu va un pas plus loin - après avoir passé avec succès l'étape CI, il déploie automatiquement les changements dans l'environnement de production ou de test. Le processus CD comprend :

  • Le build automatique de l'application
  • Le déploiement dans un environnement choisi (staging, production)
  • Les tests E2E automatiques après le déploiement
  • Le monitoring du déploiement

Outils CI/CD populaires pour les applications Next.js

Pour les applications Next.js, nous pouvons utiliser divers outils CI/CD :

  1. GitHub Actions - intégré à GitHub, offre une excellente intégration avec le dépôt
  2. GitLab CI/CD - intégré à GitLab, permet de créer des pipelines CI/CD
  3. CircleCI - plateforme CI/CD populaire avec une configuration simple
  4. Jenkins - serveur d'automatisation ouvert et extensible
  5. Vercel CI/CD - solution native pour les applications Next.js
  6. Netlify CI/CD - alternative avec des fonctionnalités similaires
  7. Travis CI - l'une des plus anciennes plateformes CI/CD

Configuration du CI/CD avec GitHub Actions

La façon la plus simple d'implémenter le CI/CD pour un projet Next.js est d'utiliser GitHub Actions si ton dépôt est hébergé sur GitHub.

1. Configuration du workflow de test

Crée le fichier .github/workflows/ci.yml dans ton dépôt :

1name: CI
2
3on:
4  push:
5    branches: [main, development]
6  pull_request:
7    branches: [main, development]
8
9jobs:
10  test:
11    runs-on: ubuntu-latest
12
13    strategy:
14      matrix:
15        node-version: [18.x]
16
17    steps:
18      - uses: actions/checkout@v3
19
20      - name: Use Node.js ${{ matrix.node-version }}
21        uses: actions/setup-node@v3
22        with:
23          node-version: ${{ matrix.node-version }}
24          cache: 'npm'
25
26      - name: Install dependencies
27        run: npm ci
28
29      - name: Lint
30        run: npm run lint
31
32      - name: Type check
33        run: npm run type-check
34
35      - name: Run tests
36        run: npm test
37
38      - name: Build
39        run: npm run build

Ce workflow s'exécute à chaque push sur les branches main et development et lors de la création d'une Pull Request vers ces branches.

2. Configuration du workflow de déploiement Vercel

Crée le fichier .github/workflows/deploy.yml :

1name: Deploy to Production
2
3on:
4  push:
5    branches: [main]
6
7jobs:
8  deploy:
9    runs-on: ubuntu-latest
10
11    steps:
12      - uses: actions/checkout@v3
13
14      - name: Deploy to Vercel
15        uses: amondnet/vercel-action@v20
16        with:
17          vercel-token: ${{ secrets.VERCEL_TOKEN }}
18          vercel-org-id: ${{ secrets.ORG_ID }}
19          vercel-project-id: ${{ secrets.PROJECT_ID }}
20          vercel-args: '--prod'

Pour que cela fonctionne, tu dois configurer les secrets suivants dans les paramètres de ton dépôt :

  • VERCEL_TOKEN - Token de l'API Vercel
  • ORG_ID - ID de l'organisation dans Vercel
  • PROJECT_ID - ID du projet dans Vercel

3. Configuration du workflow pour l'environnement de staging

1name: Deploy to Staging
2
3on:
4  push:
5    branches: [development]
6
7jobs:
8  deploy:
9    runs-on: ubuntu-latest
10
11    steps:
12      - uses: actions/checkout@v3
13
14      - name: Deploy to Vercel (Preview)
15        uses: amondnet/vercel-action@v20
16        with:
17          vercel-token: ${{ secrets.VERCEL_TOKEN }}
18          vercel-org-id: ${{ secrets.ORG_ID }}
19          vercel-project-id: ${{ secrets.PROJECT_ID }}
20          github-comment: true

Configuration du CI/CD avec GitLab CI

Si tu utilises GitLab, tu peux configurer le CI/CD à l'aide de .gitlab-ci.yml :

1image: node:18-alpine
2
3stages:
4  - test
5  - build
6  - deploy_staging
7  - deploy_production
8
9cache:
10  key: ${CI_COMMIT_REF_SLUG}
11  paths:
12    - node_modules/
13    - .next/cache/
14
15test:
16  stage: test
17  script:
18    - npm ci
19    - npm run lint
20    - npm run type-check
21    - npm test
22  except:
23    - tags
24
25build:
26  stage: build
27  script:
28    - npm ci
29    - npm run build
30  artifacts:
31    paths:
32      - .next/
33  except:
34    - tags
35
36deploy_staging:
37  stage: deploy_staging
38  script:
39    - npm install -g vercel
40    - vercel --token ${VERCEL_TOKEN} --confirm
41  environment:
42    name: staging
43    url: ${CI_PROJECT_NAME}-git-${CI_COMMIT_REF_NAME}-${CI_PROJECT_NAMESPACE}.vercel.app
44  only:
45    - development
46
47deploy_production:
48  stage: deploy_production
49  script:
50    - npm install -g vercel
51    - vercel --token ${VERCEL_TOKEN} --prod --confirm
52  environment:
53    name: production
54    url: ${CI_PROJECT_NAME}.vercel.app
55  only:
56    - main

Configuration du CI/CD pour une application Next.js avec Circle CI

Pour configurer CircleCI, crée le fichier .circleci/config.yml :

1version: 2.1
2
3orbs:
4  node: circleci/node@5.0.3
5
6jobs:
7  test:
8    docker:
9      - image: cimg/node:18.15.0
10    steps:
11      - checkout
12      - node/install-packages:
13          pkg-manager: npm
14      - run:
15          name: Run tests
16          command: npm test
17      - run:
18          name: Run linter
19          command: npm run lint
20      - run:
21          name: Check types
22          command: npm run type-check
23
24  build:
25    docker:
26      - image: cimg/node:18.15.0
27    steps:
28      - checkout
29      - node/install-packages:
30          pkg-manager: npm
31      - run:
32          name: Build application
33          command: npm run build
34      - persist_to_workspace:
35          root: .
36          paths:
37            - .next
38            - node_modules
39            - package.json
40
41  deploy-staging:
42    docker:
43      - image: cimg/node:18.15.0
44    steps:
45      - checkout
46      - attach_workspace:
47          at: .
48      - run:
49          name: Install Vercel CLI
50          command: npm install -g vercel
51      - run:
52          name: Deploy to Staging
53          command: vercel --token ${VERCEL_TOKEN} --confirm
54
55  deploy-production:
56    docker:
57      - image: cimg/node:18.15.0
58    steps:
59      - checkout
60      - attach_workspace:
61          at: .
62      - run:
63          name: Install Vercel CLI
64          command: npm install -g vercel
65      - run:
66          name: Deploy to Production
67          command: vercel --token ${VERCEL_TOKEN} --prod --confirm
68
69workflows:
70  version: 2
71  test-build-deploy:
72    jobs:
73      - test
74      - build:
75          requires:
76            - test
77      - deploy-staging:
78          requires:
79            - build
80          filters:
81            branches:
82              only: development
83      - deploy-production:
84          requires:
85            - build
86          filters:
87            branches:
88              only: main

Configuration du CI/CD avec Jenkins

Jenkins est un outil CI/CD puissant, mais il demande plus de configuration. Voici un exemple de Jenkinsfile pour un pipeline CI/CD d'une application Next.js :

1pipeline {
2    agent {
3        docker {
4            image 'node:18-alpine'
5        }
6    }
7
8    stages {
9        stage('Install Dependencies') {
10            steps {
11                sh 'npm ci'
12            }
13        }
14
15        stage('Lint') {
16            steps {
17                sh 'npm run lint'
18            }
19        }
20
21        stage('Type Check') {
22            steps {
23                sh 'npm run type-check'
24            }
25        }
26
27        stage('Test') {
28            steps {
29                sh 'npm test'
30            }
31        }
32
33        stage('Build') {
34            steps {
35                sh 'npm run build'
36            }
37        }
38
39        stage('Deploy to Staging') {
40            when {
41                branch 'development'
42            }
43            steps {
44                sh 'npm install -g vercel'
45                sh 'vercel --token ${VERCEL_TOKEN} --confirm'
46            }
47        }
48
49        stage('Deploy to Production') {
50            when {
51                branch 'main'
52            }
53            steps {
54                sh 'npm install -g vercel'
55                sh 'vercel --token ${VERCEL_TOKEN} --prod --confirm'
56            }
57        }
58    }
59
60    post {
61        always {
62            cleanWs()
63        }
64    }
65}

Tester dans le processus CI/CD

Un élément important du processus CI/CD est le test automatique. Pour les applications Next.js, nous pouvons configurer différents types de tests :

1. Tests unitaires avec Jest et React Testing Library

1// __tests__/components/Header.test.tsx
2import { render, screen } from '@testing-library/react';
3import Header from '@/components/Header';
4
5describe('Header Component', () => {
6  it('renders the logo', () => {
7    render(<Header />);
8    const logo = screen.getByAltText('Logo');
9    expect(logo).toBeInTheDocument();
10  });
11
12  it('has the correct navigation links', () => {
13    render(<Header />);
14    expect(screen.getByText('Home')).toBeInTheDocument();
15    expect(screen.getByText('About')).toBeInTheDocument();
16    expect(screen.getByText('Contact')).toBeInTheDocument();
17  });
18});

2. Tests d'intégration pour les API Routes

1// __tests__/api/user.test.ts
2import { createMocks } from 'node-mocks-http';
3import userHandler from '@/app/api/user/route';
4
5describe('/api/user endpoint', () => {
6  it('returns a user when provided with a valid ID', async () => {
7    const { req, res } = createMocks({
8      method: 'GET',
9      query: { id: '123' },
10    });
11
12    await userHandler(req, res);
13
14    expect(res._getStatusCode()).toBe(200);
15    expect(JSON.parse(res._getData())).toEqual(
16      expect.objectContaining({
17        id: '123',
18        name: expect.any(String),
19      })
20    );
21  });
22
23  it('returns 404 for non-existent user', async () => {
24    const { req, res } = createMocks({
25      method: 'GET',
26      query: { id: 'non-existent' },
27    });
28
29    await userHandler(req, res);
30
31    expect(res._getStatusCode()).toBe(404);
32  });
33});

3. Tests End-to-End avec Cypress

1// cypress/integration/navigation.spec.js
2describe('Navigation', () => {
3  it('should navigate to the about page', () => {
4    cy.visit('/');
5
6    cy.get('a[href*="about"]').click();
7
8    cy.url().should('include', '/about');
9    cy.get('h1').contains('About');
10  });
11
12  it('should navigate to the contact page', () => {
13    cy.visit('/');
14
15    cy.get('a[href*="contact"]').click();
16
17    cy.url().should('include', '/contact');
18    cy.get('h1').contains('Contact');
19  });
20});

Ajoute la configuration Cypress à GitHub Actions :

1# .github/workflows/e2e-tests.yml
2name: E2E Tests
3
4on:
5  deployment_status:
6
7jobs:
8  e2e-tests:
9    if: github.event.deployment_status.state == 'success'
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v3
13
14      - name: Install dependencies
15        run: npm ci
16
17      - name: Cypress run
18        uses: cypress-io/github-action@v5
19        with:
20          record: true
21        env:
22          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
23          CYPRESS_BASE_URL: ${{ github.event.deployment_status.target_url }}

Automatisation des tâches de développement

Au-delà du CI/CD, nous pouvons automatiser diverses autres tâches de développement :

1. Formatage automatique du code avec Husky et lint-staged

Installe les paquets nécessaires :

1npm install --save-dev husky lint-staged prettier

Mets à jour package.json :

1{
2  "scripts": {
3    "prepare": "husky install"
4  },
5  "lint-staged": {
6    "*.{js,jsx,ts,tsx}": [
7      "prettier --write",
8      "eslint --fix"
9    ],
10    "*.{json,md}": [
11      "prettier --write"
12    ]
13  }
14}

Crée un hook pre-commit :

1npx husky add .husky/pre-commit "npx lint-staged"

2. Versionnage automatique avec Semantic Release

Installe les paquets nécessaires :

1npm install --save-dev semantic-release @semantic-release/git @semantic-release/changelog

Crée le fichier .releaserc.json :

1{
2  "branches": ["main"],
3  "plugins": [
4    "@semantic-release/commit-analyzer",
5    "@semantic-release/release-notes-generator",
6    "@semantic-release/changelog",
7    "@semantic-release/npm",
8    ["@semantic-release/git", {
9      "assets": ["package.json", "CHANGELOG.md"],
10      "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
11    }],
12    "@semantic-release/github"
13  ]
14}

Ajoute un workflow pour semantic-release :

1# .github/workflows/release.yml
2name: Release
3
4on:
5  push:
6    branches: [main]
7
8jobs:
9  release:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v3
13      - name: Setup Node.js
14        uses: actions/setup-node@v3
15        with:
16          node-version: '18'
17          cache: 'npm'
18      - name: Install dependencies
19        run: npm ci
20      - name: Release
21        env:
22          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
24        run: npx semantic-release

3. Génération automatique de la documentation

Pour les projets TypeScript, tu peux automatiser la génération de la documentation :

1# .github/workflows/docs.yml
2name: Generate Documentation
3
4on:
5  push:
6    branches: [main]
7
8jobs:
9  docs:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v3
13      - name: Setup Node.js
14        uses: actions/setup-node@v3
15        with:
16          node-version: '18'
17          cache: 'npm'
18      - name: Install dependencies
19        run: npm ci
20      - name: Generate docs
21        run: npx typedoc --out docs src
22      - name: Deploy docs to GitHub Pages
23        uses: peaceiris/actions-gh-pages@v3
24        with:
25          github_token: ${{ secrets.GITHUB_TOKEN }}
26          publish_dir: ./docs

Stratégies de déploiement et de rollback

Lors du déploiement d'applications, il vaut la peine d'implémenter des stratégies de déploiement sûres :

1. Blue-Green Deployment

Le déploiement Blue-Green consiste à maintenir deux environnements de production identiques, dont un seul est actif. Dans le cas de Vercel, tu peux utiliser leur fonctionnalité intégrée « Preview Deployments » et l'aliasing de domaine :

1// scripts/blue-green-deploy.js
2const { execSync } = require('child_process');
3
4// Deploy to new environment (but not live yet)
5const output = execSync('vercel --prod').toString();
6const deploymentUrl = output.match(/https:\/\/[\w.-]+/)[0];
7
8// Run smoke tests against new deployment
9const smokeTestResult = execSync(`npm run test:smoke -- --url=${deploymentUrl}`).toString();
10
11if (smokeTestResult.includes('All tests passed')) {
12  // Switch traffic to new deployment
13  execSync(`vercel alias set ${deploymentUrl} your-production-domain.com`);
14  console.log('Successfully switched to new deployment');
15} else {
16  console.error('Smoke tests failed, aborting deployment');
17  process.exit(1);
18}

2. Canary Releases

Dans la stratégie Canary, une nouvelle version est déployée progressivement, d'abord seulement pour un petit pourcentage d'utilisateurs. Dans Next.js, tu peux y parvenir à l'aide de feature flags et de la répartition du trafic :

1// lib/feature-flags.ts
2import { useEffect, useState } from 'react';
3
4const CANARY_PERCENTAGE = 10; // 10% of users will get the new version
5
6export function useCanaryFeature(featureName: string): boolean {
7  const [isEnabled, setIsEnabled] = useState(false);
8
9  useEffect(() => {
10    // Generate a random number for the user and save it in local storage
11    let userBucket = localStorage.getItem('userBucket');
12    if (!userBucket) {
13      userBucket = Math.floor(Math.random() * 100).toString();
14      localStorage.setItem('userBucket', userBucket);
15    }
16
17    // Check if the user is in the canary group
18    const inCanaryGroup = parseInt(userBucket) < CANARY_PERCENTAGE;
19
20    // Check if the feature is active for this user
21    fetch('/api/feature-flags')
22      .then(res => res.json())
23      .then(flags => {
24        // If the flag exists and the user is in the canary group, enable the feature
25        if (flags[featureName] && inCanaryGroup) {
26          setIsEnabled(true);
27        }
28      });
29  }, [featureName]);
30
31  return isEnabled;
32}

3. Rollback automatique

Le rollback automatique du déploiement quand des problèmes surviennent est une pratique clé :

1# .github/workflows/deploy-monitor-rollback.yml
2name: Deploy, Monitor & Rollback
3
4on:
5  push:
6    branches: [main]
7
8jobs:
9  deploy:
10    runs-on: ubuntu-latest
11    outputs:
12      deployment_id: ${{ steps.deploy.outputs.deployment_id }}
13    steps:
14      - uses: actions/checkout@v3
15      - name: Deploy to Vercel
16        id: deploy
17        uses: amondnet/vercel-action@v20
18        with:
19          vercel-token: ${{ secrets.VERCEL_TOKEN }}
20          vercel-org-id: ${{ secrets.ORG_ID }}
21          vercel-project-id: ${{ secrets.PROJECT_ID }}
22          vercel-args: '--prod'
23
24
25  monitor:
26    needs: deploy
27    runs-on: ubuntu-latest
28    steps:
29      - name: Wait for 2 minutes
30        run: sleep 120
31
32      - name: Check for errors
33        id: check_errors
34        run: |
35          ERROR_COUNT=$(curl -s "https://api.example.com/monitoring/errors?since=$(date -u -d '2 minutes ago' +%Y-%m-%dT%H:%M:%SZ)" | jq '.count')
36          echo "::set-output name=error_count::$ERROR_COUNT"
37          if [ "$ERROR_COUNT" -gt "10" ]; then
38            echo "Error count too high ($ERROR_COUNT). Failing job."
39            exit 1
40          fi
41
42      - name: Monitor Core Web Vitals
43        id: web_vitals
44        run: |
45          LCP=$(curl -s "https://api.example.com/monitoring/web-vitals" | jq '.lcp')
46          if [ "$LCP" -gt "2500" ]; then
47            echo "LCP too high ($LCP ms). Failing job."
48            exit 1
49          fi
50
51  rollback:
52    needs: [deploy, monitor]
53    runs-on: ubuntu-latest
54    if: ${{ failure() }}
55    steps:
56      - name: Rollback to previous deployment
57        uses: amondnet/vercel-action@v20
58        with:
59          vercel-token: ${{ secrets.VERCEL_TOKEN }}
60          vercel-org-id: ${{ secrets.ORG_ID }}
61          vercel-project-id: ${{ secrets.PROJECT_ID }}
62          vercel-args: '--prod --scope myorg'  # Rollback to previous deployment

Gérer les variables d'environnement

La gestion sécurisée des variables d'environnement est un aspect critique du CI/CD :

1. Stocker les variables d'environnement dans le système CI/CD

La plupart des systèmes CI/CD proposent un stockage sécurisé des secrets, par ex. GitHub Secrets :

1# .github/workflows/deploy.yml
2name: Deploy
3
4on:
5  push:
6    branches: [main]
7
8jobs:
9  deploy:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v3
13
14      - name: Create .env file
15        run: |
16          cat > .env.production << EOL
17          DATABASE_URL=${{ secrets.DATABASE_URL }}
18          API_KEY=${{ secrets.API_KEY }}
19          NEXT_PUBLIC_ANALYTICS_ID=${{ secrets.NEXT_PUBLIC_ANALYTICS_ID }}
20          EOL
21
22      - name: Deploy
23        run: npm run deploy

2. Rotation automatique des secrets

Pour les applications critiques, il vaut la peine d'implémenter une rotation automatique des secrets :

1# .github/workflows/rotate-secrets.yml
2name: Rotate Secrets
3
4on:
5  schedule:
6    - cron: '0 0 1 * *'  # Every month
7
8jobs:
9  rotate:
10    runs-on: ubuntu-latest
11    steps:
12      - name: Generate new API key
13        id: new_key
14        run: echo "::set-output name=api_key::$(openssl rand -base64 32)"
15
16      - name: Update API service with new key
17        run: |
18          curl -X POST -H "Authorization: Bearer ${{ secrets.ADMIN_TOKEN }}"           -d '{"api_key": "${{ steps.new_key.outputs.api_key }}"}'           https://api.example.com/admin/update-key
19
20      - name: Update GitHub secret
21        uses: gliech/create-github-secret-action@v1
22        with:
23          name: API_KEY
24          value: ${{ steps.new_key.outputs.api_key }}
25          pa_token: ${{ secrets.PA_TOKEN }}

Monitoring du déploiement

Un élément clé du processus CI/CD est le monitoring du déploiement, pour détecter rapidement les problèmes potentiels et y réagir :

1. Intégration avec les services de monitoring

1# .github/workflows/deploy-and-monitor.yml
2name: Deploy and Monitor
3
4on:
5  push:
6    branches: [main]
7
8jobs:
9  deploy:
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v3
13      - name: Deploy to Vercel
14        uses: amondnet/vercel-action@v20
15        with:
16          vercel-token: ${{ secrets.VERCEL_TOKEN }}
17          vercel-org-id: ${{ secrets.ORG_ID }}
18          vercel-project-id: ${{ secrets.PROJECT_ID }}
19          vercel-args: '--prod'
20
21      - name: Notify Datadog of deployment
22        run: |
23          curl -X POST "https://api.datadoghq.com/api/v1/events"           -H "Content-Type: application/json"           -H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}"           -d '{"title":"Deployment to production","text":"Deployed commit ${{ github.sha }} to production","tags":["environment:production","team:frontend"],"alert_type":"info"}'

2. Tests automatiques après déploiement

1# .github/workflows/post-deploy-tests.yml
2name: Post-deployment Tests
3
4on:
5  deployment_status:
6
7jobs:
8  smoke-tests:
9    if: github.event.deployment_status.state == 'success'
10    runs-on: ubuntu-latest
11    steps:
12      - uses: actions/checkout@v3
13
14      - name: Run smoke tests
15        run: |
16          npm ci
17          npm run test:smoke -- --url=${{ github.event.deployment_status.target_url }}
18
19      - name: Post results to Slack
20        if: always()
21        uses: slackapi/slack-github-action@v1
22        with:
23          payload: |
24            {
25              "text": "Smoke test results: ${{ job.status }}\nDeployment URL: ${{ github.event.deployment_status.target_url }}"
26            }
27        env:
28          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Résumé

Implémenter le CI/CD pour les applications Next.js est un élément clé du processus moderne de développement logiciel, qui apporte de nombreux bénéfices :

  1. Déploiement plus rapide des changements - l'automatisation du processus réduit le temps nécessaire pour livrer de nouvelles fonctionnalités
  2. Meilleure qualité du code - les tests automatiques et l'analyse du code garantissent une meilleure qualité
  3. Moins de risque d'erreurs - l'automatisation élimine les erreurs humaines
  4. Feedback plus rapide - les développeurs reçoivent un retour immédiat sur la qualité du code
  5. Productivité accrue - les développeurs se concentrent sur le développement de fonctionnalités, pas sur des processus manuels

Un pipeline CI/CD correctement configuré est un investissement qui se rentabilise en temps gagné, en meilleure qualité de code et en déploiements plus fiables. Quelle que soit la plateforme choisie (GitHub Actions, GitLab CI, CircleCI, Jenkins), l'essentiel est d'adapter le processus aux besoins spécifiques du projet et de l'équipe.

Code de cette leçon : App.tsx
1import React, { useState } from 'react';
2
3// Demo : CI/CD et automatisation du déploiement
4interface PipelineStage {
5  id: string;
6  name: string;
7  icon: string;
8  status: 'pending' | 'running' | 'success' | 'failed' | 'skipped';
9  duration?: number;
10  steps: string[];
11}
12
13function CICDDemo() {
14  const [stages, setStages] = useState<PipelineStage[]>([
15    { id: 'install', name: 'Install', icon: 'I', status: 'pending', steps: ['npm ci', 'Cache dependencies'] },
16    { id: 'lint', name: 'Lint', icon: 'L', status: 'pending', steps: ['ESLint', 'Prettier check', 'TypeScript check'] },
17    { id: 'test', name: 'Test', icon: 'T', status: 'pending', steps: ['Unit tests', 'Integration tests', 'Coverage report'] },
18    { id: 'build', name: 'Build', icon: 'B', status: 'pending', steps: ['next build', 'Bundle analysis', 'Optimize assets'] },
19    { id: 'deploy', name: 'Deploy', icon: 'D', status: 'pending', steps: ['Deploy to staging', 'Health check', 'Promote to production'] },
20  ]);
21  const [activeProvider, setActiveProvider] = useState<'github' | 'gitlab' | 'vercel'>('github');
22  const [isRunning, setIsRunning] = useState(false);
23  const [failAt, setFailAt] = useState<string>('none');
24
25  const statusColors: Record<string, string> = {
26    pending: '#64748b', running: '#f59e0b', success: '#10b981', failed: '#ef4444', skipped: '#8892b0',
27  };
28
29  const statusIcons: Record<string, string> = {
30    pending: 'o', running: '~', success: 'V', failed: 'X', skipped: '-',
31  };
32
33  const runPipeline = async () => {
34    setIsRunning(true);
35    setStages(prev => prev.map(s => ({ ...s, status: 'pending', duration: undefined })));
36
37    for (let i = 0; i < stages.length; i++) {
38      const stage = stages[i];
39
40      setStages(prev => prev.map((s, idx) => idx === i ? { ...s, status: 'running' } : s));
41
42      const duration = Math.floor(Math.random() * 3000) + 1000;
43      await new Promise(r => setTimeout(r, duration));
44
45      if (failAt === stage.id) {
46        setStages(prev => prev.map((s, idx) =>
47          idx === i ? { ...s, status: 'failed', duration } :
48          idx > i ? { ...s, status: 'skipped' } : s
49        ));
50        setIsRunning(false);
51        return;
52      }
53
54      setStages(prev => prev.map((s, idx) => idx === i ? { ...s, status: 'success', duration } : s));
55    }
56
57    setIsRunning(false);
58  };
59
60  const ciConfigs: Record<string, string> = {
61    github: "# .github/workflows/deploy.yml\nname: Deploy\non:\n  push:\n    branches: [main]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm ci\n      - run: npm run lint\n      - run: npm run test\n      - run: npm run build\n      - run: npx vercel --prod",
62    gitlab: "# .gitlab-ci.yml\nstages:\n  - install\n  - lint\n  - test\n  - build\n  - deploy\n\ninstall:\n  stage: install\n  script: npm ci\n  cache:\n    paths: [node_modules/]\n\ntest:\n  stage: test\n  script: npm run test\n\nbuild:\n  stage: build\n  script: npm run build",
63    vercel: "// vercel.json\n{\n  \"buildCommand\": \"npm run build\",\n  \"devCommand\": \"npm run dev\",\n  \"installCommand\": \"npm ci\",\n  \"framework\": \"nextjs\",\n  \"github\": {\n    \"autoAlias\": true,\n    \"silent\": true\n  }\n}",
64  };
65
66  return (
67    <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#e0e0e0', fontFamily: 'system-ui' }}>
68      <h1 style={{ color: '#64ffda', marginBottom: '8px' }}>CI/CD Pipeline</h1>
69      <p style={{ color: '#8892b0', marginBottom: '24px' }}>Automatisation du déploiement d'applications Next.js</p>
70
71      <div style={{ display: 'flex', gap: '8px', marginBottom: '20px' }}>
72        {[
73          { id: 'github' as const, label: 'GitHub Actions' },
74          { id: 'gitlab' as const, label: 'GitLab CI' },
75          { id: 'vercel' as const, label: 'Vercel' },
76        ].map(p => (
77          <button key={p.id} onClick={() => setActiveProvider(p.id)} style={{
78            padding: '8px 16px', borderRadius: '8px', border: 'none', cursor: 'pointer',
79            background: activeProvider === p.id ? '#64ffda' : '#1a1a2e', color: activeProvider === p.id ? '#0f0f23' : '#8892b0', fontWeight: 'bold',
80          }}>{p.label}</button>
81        ))}
82      </div>
83
84      <div style={{ background: '#1a1a2e', padding: '20px', borderRadius: '12px', border: '1px solid #333', marginBottom: '24px' }}>
85        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
86          <h3 style={{ color: '#64ffda', margin: 0 }}>Pipeline Stages</h3>
87          <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
88            <select value={failAt} onChange={e => setFailAt(e.target.value)} style={{ padding: '6px 10px', borderRadius: '6px', background: '#0f0f23', color: '#fff', border: '1px solid #333', fontSize: '12px' }}>
89              <option value="none">No failure</option>
90              {stages.map(s => <option key={s.id} value={s.id}>Fail at {s.name}</option>)}
91            </select>
92            <button onClick={runPipeline} disabled={isRunning} style={{ padding: '8px 16px', borderRadius: '6px', background: isRunning ? '#333' : '#64ffda', color: '#0f0f23', border: 'none', cursor: isRunning ? 'default' : 'pointer', fontWeight: 'bold' }}>
93              {isRunning ? 'Running...' : 'Run Pipeline'}
94            </button>
95          </div>
96        </div>
97
98        <div style={{ display: 'flex', gap: '16px', alignItems: 'flex-start', flexWrap: 'wrap' }}>
99          {stages.map((stage, i) => (
100            <React.Fragment key={stage.id}>
101              <div style={{ textAlign: 'center', minWidth: '120px' }}>
102                <div style={{
103                  width: '50px', height: '50px', borderRadius: '50%', margin: '0 auto 8px',
104                  border: '3px solid ' + statusColors[stage.status],
105                  display: 'flex', alignItems: 'center', justifyContent: 'center',
106                  background: stage.status === 'running' ? statusColors[stage.status] + '30' : 'transparent',
107                  fontSize: '18px', fontWeight: 'bold', color: statusColors[stage.status],
108                }}>
109                  {statusIcons[stage.status]}
110                </div>
111                <div style={{ fontWeight: 'bold', fontSize: '14px', color: statusColors[stage.status] }}>{stage.name}</div>
112                {stage.duration && <div style={{ fontSize: '11px', color: '#8892b0' }}>{(stage.duration / 1000).toFixed(1)}s</div>}
113                <div style={{ marginTop: '8px' }}>
114                  {stage.steps.map((step, j) => (
115                    <div key={j} style={{ fontSize: '11px', color: '#555', padding: '1px 0' }}>{step}</div>
116                  ))}
117                </div>
118              </div>
119              {i < stages.length - 1 && <div style={{ paddingTop: '20px', color: '#333', fontSize: '20px' }}>-&gt;</div>}
120            </React.Fragment>
121          ))}
122        </div>
123      </div>
124
125      <div style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', border: '1px solid #333' }}>
126        <h3 style={{ color: '#f59e0b', fontSize: '14px', marginTop: 0 }}>Configuration ({activeProvider}) :</h3>
127        <pre style={{ fontSize: '12px', color: '#e0e0e0', margin: 0, overflow: 'auto', whiteSpace: 'pre-wrap' }}>{ciConfigs[activeProvider]}</pre>
128      </div>
129    </div>
130  );
131}
132
133export default CICDDemo;

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. CI/CD signifie :

  2. 2. Quelle est la différence entre Continuous Delivery et Continuous Deployment ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée un workflow GitHub Actions déclenché par un push sur la branche main : checkout, setup-node, installation des dépendances, tests, build, puis déploiement sur Vercel avec le jeton du secret VERCEL_TOKEN.

  • Tri vertical

    Place les éléments dans le bon ordre : push du code → lancer les tests → builder l'app

  • Tri vertical

    Place la syntaxe de configuration du trigger d'un workflow GitHub Actions :

  • Éditeur de code

    Crée un endpoint API de health check avec connectivité à la base de données, état des services externes et métriques système

Articles utiles