Aller au contenu
gaetancottrez.dev

Pourquoi et comment j’ai migré mon blog WordPress vers Astro ?

Published:le  à 09:00 | (17 min de lecture)
Pourquoi et comment j’ai migré mon blog WordPress vers Astro ?

Table des matières

Ouvrir table des matières

WordPress : une hérésie pour le développement web moderne?

WordPress n’est plus à présenter. C’est un CMS mondialement connu, conçu sous PHP/MySQL pour son moteur et bien évidemment HTML/CSS/JavaScript (coucou jQuery) pour le rendu.

Bien sûr, WordPress est accessible pour les développeurs, mais il a été conçu pour que des non-développeurs puissent concevoir leur blog eux-mêmes en mode clic-clic.

D’ailleurs, au début, WordPress était un moteur pour produire des blogs et aujourd’hui il est possible de le transformer en sites internet complets et complexes grâce à la partie thèmes et plugins qu’il nous offre.

Que ce soit WordPress ou un autre CMS, l’objectif premier est de se concentrer sur la production de contenu plutôt que sur la partie technique.

Je me suis tourné naturellement vers WordPress pour sa popularité et avec pour objectif de rester concentré uniquement sur le contenu que je voulais partager et de ne pas m’embêter avec la technique (un comble pour un développeur, me direz-vous ?).

Les années passent, j’ai refait le design du blog plusieurs fois, installé de plus en plus de plugins dessus… Jusqu’à prendre conscience que WordPress est devenu une hérésie aujourd’hui.

Wordpress est-il une hérésie technique ?

fonctionnement de WordPress

Quand vous créez un site internet ou un blog, qu’est-ce que vous voulez obtenir ?

Un résultat élégant, rapide, accessible, optimisé et performant. Il est difficile aujourd’hui d’obtenir ce résultat avec un site sous WordPress à moins de développer son propre thème et ses propres extensions pour les besoins de son site.

Mais même avec ça, on est obligé d’avoir recours à une extension de génération de cache client (WP Rocket) qui va en réalité générer une version statique de votre blog à intervalles réguliers.

On peut même le combiner avec du cache serveur (Varnish) pour améliorer encore la vitesse.

En résumé, quand vous utilisez WordPress avec un thème et des extensions, puis que vous mixez le tout avec un WP Rocket : vous obtenez à la fin une version statique de votre site internet avec un DOM monstrueux et loin d’être optimal.

C’est le constat que j’ai remarqué avec mon propre blog et je me suis posé cette question :

Pourquoi ne pas produire directement cette version statique du blog mais de manière élégante, rapide, accessible, optimisée et performante ?

C’est le choix que j’ai donc fait : produire directement la version statique de mon blog de manière innovante.

Mes besoins techniques

Pour bien faire ma migration de WordPress, j’ai décidé de passer au crible mon WordPress pour voir dans le détail toutes les fonctionnalités réellement utiles.

J’en ai fait un MindMap en catégorisant les plugins et fonctionnalités à garder (en vert) et celles à jeter (en rouge).

inventaire des fonctionnalités de mon blog WordPress

Comme vous pouvez le voir, la moitié est à jeter 😄.

Bien entendu, je n’ai pas listé les fonctionnalités classiques mais essentielles d’un blog, par exemple l’édition de nouveaux articles, le SEO, une barre de recherche, un système de catégories/tags, un sitemap, un flux RSS, etc…

Mes besoins en termes d’hébergement

Actuellement, mon WordPress est hébergé chez l’hébergeur O2Switch 🔗. C’est un hébergement partagé qui offre un cPanel pour :

Le coût de l’hébergement me coûte environ 100 euros par an + 10 euros pour le nom de domaine = 110 euros. Et je dois le payer annuellement.

En quittant WordPress, je me suis fixé comme objectif de trouver un hébergement évolutif, avec lequel je ne suis pas “marié”, et, cerise sur le gâteau, moins cher que O2Switch.

Les fonctionnalités à développer frontend/backend

les fonctionnalités à développer pour mon blog Astro

J’ai décidé de mettre en place une architecture Front/API en reprenant mon état des lieux de WordPress pour évaluer les fonctionnalités que je dois développer côté backend et côté frontend.

Il y a trois fonctionnalités à développer pour le backend :

  1. La réception d’un formulaire de contact qui est utilisé par le blog et mon autre site, gaetancottrez.dev 🔗, avec un envoi d’e-mail.
  2. L’inscription à ma newsletter via le service ActiveCampaign.
  3. Re-créer un système équivalent à Revive Old Post de WordPress pour repartager mes articles sur Facebook.

Pour le frontend, il y en a un peu plus :

  1. Captcha où j’ai utilisé le reCaptcha de Google.
  2. What Would Seth Godin Do.
  3. Social Share.
  4. Related Article.
  5. Buy me a Coffee.
  6. Popup ActiveCampaign.
  7. Ribbon ActiveCampaign.
  8. Widget ActiveCampaign.
  9. Formulaire de contact.
  10. Intégration de Commentbox.io.

Laissez-moi maintenant vous expliquer en détail mes choix techniques.

Mon choix technique pour la partie Frontend : Astro

Depuis ces dernières années, de nouvelles technologies et de nouveaux frameworks ont émergé.

Ses nouvelles technologies reviennent à l’essentiel : générer une version statique (SSG) d’un site internet ou d’un blog de manière simple et efficace tout en intégrant les standards du web.

C’est ce que je recherchais dans ma quête de migration de mon blog WordPress.

J’aurais pu me tourner comme beaucoup l’ont fait vers Gatsby 🔗. Mais n’étant pas un grand fan de React, je ne voulais pas d’un framework qui a été bâti autour cette librairie.

Pour la partie Front, j’ai décidé de me diriger sur Astro 🔗.

Alors, pourquoi AstroJS ? Voici quelques points intéressant qui ont retenu mon attention:

Bien entendu, j’ai pris en compte d’autres points qui sont essentiels dans le choix de cette techno.

Si vous voulez savoir comment je m’y prends pour choisir une nouvelle techno sans suivre les autres comme un mouton, c’est par ici.

Petite aparté : comme je l’ai dit plus haut, je ne porte pas particulièrement React dans mon coeur et ceux depuis sa création. Néanmoins j’ai voulu me faire un réel avis sur cette librairie en l’utilisant dans AstroJS pour mon blog. Si vous êtes intéressé par mon avis sur React, n’hésitez pas à me le faire savoir en commentaire.

J’ai d’ailleurs choisi le thème Astro paper 🔗 qui a été la base de mon blog et où j’ai développé toutes les fonctionnalités que j’ai listé un peu plus haut.

J’ai donc créé quelques composants en React (où le thème présentait certains composants déjà développé en React) histoire de me faire un avis sur cette librairie.

Un Backend sur mesure en TypeScript/Node.js

C’est mon domaine de prédilection et c’est celui où je m’amuse le plus : le backend. J’ai mis en pratique toute mon expérience et mes connaissances dans la réalisation de cette architecture backend.

J’ai structuré le code du backend sous la forme de package grâce à turbo 🔗.

├── packages
   ├── domain
   ├── application
   ├── infrastructure
   ├── cron
   ├── api
   ├── serverless

Chacun de ces dossiers a un rôle bien défini autour d’une architecture hexagonale.

Pourquoi je suis parti sur cette architecture ?

La question est légitime sachant que, côté backend, je n’ai que trois fonctionnalités à développer, mais comme je suis un fervent défenseur de la partie backend, j’aime faire les choses correctement.

Je ne sais pas où je vais aller avec mon backend. Il y aura certainement de nouvelles fonctionnalités à implanter, et probablement beaucoup plus complexes que celles que j’ai développées.

L’une de mes attentes est de ne pas être dépendant d’une techno en particulier. Ainsi, je suis parti pour la couche API sur un NestJS, mais si je veux en changer demain, pas de problème : il me suffit de changer la techno de l’API, d’implanter mes routes et les appels du code métier sans tout recoder.

De même, mon code métier pourra s’exécuter sur des infrastructures différentes (dans mon cas, l’API et la partie cron).

Explication de l’architecture

Tout d’abord, nous avons les trois couches de l’architecture hexagonale qui fonctionnent ainsi :

domain ├─> application ├─>  infrastructure
  1. Le domain fonctionne de manière autonome et il contient l’ensemble des règles métiers. Il ne connait que lui-même.
  2. L’application orchestre ses règles métiers par des uses-cases. Elle implémente le domaine mais ne connait pas l’infrastructure.
  3. L’infrastructure est la partie qui va implanter l’application (et indirectement le domaine sans le savoir).

Sauf que dans mon cas, ce n’est pas vraiment ça.

La couche d’infrastructure va s’occuper de répondre aux contrats fixés par l’application et va faire fonctionner tout ce petit monde dans une condition de réalité d’infrastructure.

Ainsi, j’y définis :

Les dossiers cron et api auraient pu être placés dans le dossier infrastructure, mais j’ai trouvé beaucoup plus simple et cohérent de les séparer en des dossiers distincts.

api  ├─> infrastructure ├─> application
cron ├─> infrastructure ├─> application

Le package api démarre une fois que le package infrastructure a bien démarré pour ensuite utiliser le package d’application. Et c’est la même chose pour le package cron.

On veut du code !

Voici le code du package API qui permet de démarrer par le package infrastructure :

async function CreateApp(httpAdapter = {}) {
  let app = await NestFactory.create<NestExpressApplication>(
    AppModule,
    httpAdapter
  );
  app = app.enableVersioning({
    type: VersioningType.URI,
    defaultVersion: CURRENT_VERSION,
  });
  // Express
  app.disable("x-powered-by");
  app.setGlobalPrefix("");
  app.useStaticAssets(join(__dirname, "..", "public"));
  app.setViewEngine("hbs");
  app.use(text());
  // Swagger
  const options = new DocumentBuilder()
    .setTitle(container.resolve(tokens.TITLE_API))
    .setDescription("")
    .setVersion(CURRENT_VERSION)
    .addBasicAuth()
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup("", app, document);

  return app;
}

async function bootstrap() {
  const app = await CreateApp();
  await app.listen(container.resolve(tokens.PORT) ?? 3000);
  return app;
}
infra().then(bootstrap);

C’est justement ce petit bout de code tout simple qui permet de démarrer l’infrastructure et l’API :

infra().then(bootstrap);

Et voici, en partie, le code de la fonction infrastructure :

const infra = async function () {
  const startTime = Date.now();

  const infraLogger = new Logger("INFRA");

  infraLogger.info("Loading configuration from remote mongo");

  dotenvConfig({ debug: true, path: resolve(__dirname, "../../../.env") });
  await config(infraLogger);

  infraLogger.info(
    `${container.resolve(tokens.TITLE_API)} : ${container.resolve(
      tokens.API_BASE_URL
    )}`
  );

  infraLogger.debug("Registering services");

  for (const clazz of Object.values(services)) {
    if (
      !container.isRegistered(
        clazz as new (...args: any[]) => Partial<IService>
      )
    ) {
      continue;
    }
    const instance: Partial<IService> = container.resolve(
      clazz as new (...args: any[]) => Partial<IService>
    );
    if (instance.start) {
      infraLogger.debug(`Starting service: ${instance.constructor.name}....`);
      await instance.start();
      infraLogger.debug(`Service started: ${instance.constructor.name}`);
    }
  }

  for (const clazz of Object.values(repositories)) {
    if (!container.isRegistered(clazz)) {
      continue;
    }
    container.resolve(clazz as new (...args: any[]) => any);
  }

  infraLogger.info(`Infrastructure is setup in ${Date.now() - startTime}ms`);
};

Rien de bien compliqué en soi ! Simple et efficace, comme vous le voyez !

Les différents hébergements que j’ai choisi

Mon blog a, de façon modeste, un petit trafic (environ 200 visites uniques par jour). Je recherche donc un hébergement évolutif que je pourrais ajuster facilement en cas de pics dans mon trafic.

Je pars du principe que je peux déployer n’importe où et n’importe quand. Si en plus cela peut être rapide, facile et pas cher sans être lié à l’hébergeur, c’est encore mieux.

Je me suis donc tourné vers Vercel 🔗 pour déployer mon blog Astro. J’aurais pu utiliser Netlify 🔗 mais j’ai voulu tester Vercel pour me faire un avis.

Le moins que l’on puisse dire c’est que la version gratuite sera amplement suffisante.

Pour la partie backend, j’ai choisi d’utiliser les Lambda d’AWS 🔗. L’un des avantages de Lambda, c’est le coût. On paie uniquement à l’exécution (on a le droit à un million d’exécutions par mois pendant 12 mois).

Comme le backend sera utilisé principalement pour soumettre un formulaire de contact ainsi que mon formulaire de newsletter.

Autant dire qu’au début mon API ne sera pas énormément sollicitée, donc si je peux avoir un coût dérisoire, voire proche du nul, alors je prends !

Vous l’aurez donc deviné, mais je baisse drastiquement le coût de mon hébergement à 0 euro par an pour le moment.

J’ai fait un petit schéma pour vous montrer comment tout ce petit monde va communiquer ensemble :

Schéma d'infrastructure d'apprendre la programmation

Le package serverless

Il y a un dernier package sur lequel je n’ai volontairement pas parlé lors de mes explications sur mon backend.

Sachez d’abord que le package api et le package cron peuvent fonctionner de manière classique.

Je peux les faire tourner localement, bien sûr, mais ils peuvent également tourner n’importe où, comme sur fly.io 🔗, Heroku 🔗, Scalingo 🔗 ou Render 🔗.

Cependant, comme j’ai choisi d’héberger mon backend en mode lambda sur AWS, les packages actuels ne peuvent pas fonctionner dans l’état.

Pourquoi ? Parce que les lambdas fonctionnent de base de manière autonome, c’est-à-dire que tout doit être dans un seul et même fichier, y compris les dépendances.

J’ai dit “de base” parce que dans la pratique, il est possible d’externaliser les dépendances node_modules dans un bucket S3, par exemple, et de dire à la lambda où elles se trouvent, mais c’est assez fastidieux comme manipulation.

Bien entendu, la plupart des build en TypeScript/JavaScript ne sont pas en un seul fichier. Si l’on prend mon API NestJS, on obtient un dossier dist qui contient beaucoup de fichiers et qui fait référence au dossier node_modules:

contenu du dossier dist de NestJS

Transformer son build en un fichier

Heureusement, il existe une solution open source de Vercel baptisée ncc 🔗 pour réaliser cette opération.

Et je dois dire que cette solution est assez impressionnante car elle ne se contente pas de prendre votre code et de le mettre tout en un seul fichier avec les node_modules.

Elle va beaucoup plus loin en minifiant, mais surtout elle n’importe que les dépendances que vous utilisez réellement dans votre code (un peu comme le cherry-pick de git). Vous obtenez ainsi un build optimisé et prêt à l’emploi pour votre lambda.

La lambda pour l’API

Transformer le build de l’API en un seul fichier ne suffit pas. Il faut exécuter l’API dans du code que les lambdas AWS peuvent comprendre.

Rien de bien sorcier en soi. Il suffit d’utiliser les bons packages AWS :

import { Handler, Context } from "aws-lambda";
import { Server } from "http";
import { createServer, proxy } from "aws-serverless-express";
import { eventContext } from "aws-serverless-express/middleware";
import { ExpressAdapter } from "@nestjs/platform-express";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const express = require("express");
import infra from "@backend/infrastructure";
import { CreateApp } from "@backend/api";

const binaryMimeTypes: string[] = [];

let cachedServer: Server;

async function bootstrapServer(): Promise<Server> {
  if (!cachedServer) {
    await infra();

    const expressApp = express();

    const nestApp = await CreateApp(new ExpressAdapter(expressApp));
    nestApp.use(eventContext());
    await nestApp.init();
    cachedServer = createServer(expressApp, undefined, binaryMimeTypes);
  }
  return cachedServer;
}

export const handler: Handler = async (event: any, context: Context) => {
  cachedServer = await bootstrapServer();
  return await proxy(cachedServer, event, context, "PROMISE").promise;
};

On importe nos packages api et infrastructure (qui ont déjà été build dans leur dossier respectif), on bootstrape un peu différemment notre API pour l’utiliser dans notre handler, et le tour est joué !

Une fois que ce code est prêt, il suffit de le compiler avec la commande ncc :

ncc build -m -o dist/api src/api.ts

On obtient à la sortie un build prêt à être utilisé en mode lambda sur AWS.

Déploiement de la lambda API sur AWS

Pour faciliter le déploiement, c’est ma GitHub Action qui s’en occupe, mais elle fait appel à un utilitaire bien pratique nommé : https://www.serverless.com 🔗.

Le package npm serverless permet de faciliter le déploiement de la Lambda, mais également de tout l’environnement dont vous avez besoin pour faire fonctionner la lambda (Scheduled Task, API Gateway, etc.).

Utilisez Serverless, c’est un gain de temps considérable pour déployer dans AWS, mais aussi pour gérer ses environnements. Grâce à lui, vous pouvez déployer des stacks AWS pour vos previews, staging, ou production.

La configuration de votre stack et surtout de comment votre lambda doit se déployer se fait avec une configuration serverless.yml :

functions:
  api:
    handler: dist/api/index.handler
    timeout: 30
    events:
      - http:
          method: any
          path: /{proxy+}
          cors:
            headers: "*"
            origin: "*"
            allowCredentials: true

Cette configuration me permet de spécifier que je veux, pour ma stack AWS, une API Gateway qui agira uniquement comme un proxy pour déclencher ma lambda API qui s’occupera de traiter les requêtes.

Et voici la partie de ma GitHub Action qui déploie en production, par exemple :

- name: Deploy Serverless (production)
  run: npx serverless deploy --stage prod
  working-directory: packages/serverless
  if: needs.stage.outputs.stage_name == 'production'

C’est la commande npx serverless deploy qui va permettre de déployer tout ce que je vous ai montré sur AWS.

Bien entendu, je ne me voyais pas récupérer tous mes articles, médias et commentaires manuellement. Il existe quelques dépôts qui permettent de récupérer le contenu de WordPress dans d’autres formats. Moi, je me suis tourné vers ce dépôt : https://github.com/lonekorean/wordpress-export-to-markdown 🔗.

Le principe est simple : exporter un zip de tout le blog WordPress, puis le lancer dans la moulinette de ce repo.

Résultat : on obtient un dossier qui contient tous les médias utilisés dans les articles et chacun des articles au format markdown, tout simplement !

Pour les commentaires de tous les articles, je me suis basé sur la documentation de CommentBox pour gérer l’importation.

Migration des données WordPress vers le nouveau blog

Bien entendu, je ne me voyais pas récupérer tous mes articles, médias et commentaires manuellement. Il existe quelques dépôts qui permettent de récupérer le contenu de WordPress dans d’autres formats.

Migration des articles

Moi, je me suis tourné vers ce dépôt : https://github.com/lonekorean/wordpress-export-to-markdown. 🔗

Le principe est simple : exporter un zip de tout le blog WordPress, puis le lancer dans la moulinette de ce repo.

Résultat : on obtient un dossier qui contient tous les médias utilisés dans les articles et chacun des articles au format markdown, tout simplement !

Migration des commentaires

Pour les commentaires de tous les articles, je me suis basé sur la documentation de CommentBox 🔗 pour gérer l’importation.

Migration des redirections

Le système de redirection est déjà intégré dans Astro, mais il faut que je récupère celles que j’ai gérées dans WordPress.

Comme je n’avais pas énormément de redirections à migrer, j’ai décidé de les gérer manuellement et cela se passe au niveau du fichier de config d’Astro :

redirects: {
  "/debuter-guide-7-erreurs-a-eviter-pour-programmer": {
    status: 301,
      destination:
    "/debuter-guide-7-erreurs-a-eviter-pour-bien-debuter-en-programmation",
    },
  "/category/non-classe": {
    status: 301,
      destination: "/category/methodologie",
    },
  "/7-erreurs-a-eviter-bien-debuter-programmation": {
    status: 301,
      destination: "/7-erreurs-a-eviter-pour-bien-debuter-en-programmation",
    },
  "/debuter-guide-7-erreurs-a-eviter-pour-bien-debuter-en-programmation": {
    status: 301,
      destination: "/debuter-en-programmation",
    },
  "/m3-journal-un-outil-anti-procrastination": {
    status: 301,
      destination: "/m3-journal-outil-anti-procrastination",
    },
  "/faut-il-devenir-un-developpeur-backend-frontend-ou-fullstack": {
    status: 301,
      destination: "/devenir-developpeur-backend-frontend-fullstack",
    },
  "/versionner-son-code-2": {
    status: 301,
      destination: "/versionner-son-code-git",
    },
  "/tag/developpement-weeb": {
    status: 301,
      destination: "/tag/developpement-web",
    },
  "tester-envoi-e-mails-dans-son-application-local": {
    status: 301,
      destination: "/tester-envoi-e-mails-application-locale-guide",
    },
  "/principales-erreurs-en-tant-que-developpeur": {
    status: 301,
      destination: "/principales-erreurs-developpeur-guide",
    },
  "/6-raisons-de-devenir-programmeur": {
    status: 301,
      destination: "/devenir-programmeur-raisons",
    },
  "/guide-des-principaux-langages-de-programmation": {
    status: 301,
      destination: "/principaux-langages-de-programmation",
    },
  "/github-copilot-ai": {
    status: 301,
      destination: "/github-copilot",
    },
  "/stage-de-developpeur-astuces": {
    status: 301,
      destination: "/stage-de-developpeur",
    },
  "/derniers-articles": {
    status: 301,
      destination: "/posts",
    },
},

Epilogue

Il me reste une dernière chose à vous parler: la mise en ligne de mon nouveau blog.

Une fois le back et le front déployé, il manquait juste 4 choses à faire :

Si vous avez des questions sur mon article, n’hésitez pas à me les soumettre.

Je me ferais une joie à vous répondre !

Vous pourriez aussi aimer

Guide des principaux langages de programmation

Guide des principaux langages de programmation

Comment être productif en programmation ?

Comment être productif en programmation ?

Article précédent
Guide Complet pour Configurer un Monorepo TypeScript avec Turbo
Article suivant
Comment bien choisir les technos que vous allez utiliser ?