Astro, le framework web JS multi-pages

Astro est un nouveau framework1 web JS (JavaScript) qui se place au même niveau que les populaires Next.js, Nuxt ou encore SvelteKit. Comme ses homologues, il propose de développer un site web intégralement en JavaScript (ou plutôt TypeScript de nos jours2) et de publier le site, soit de façon dynamique avec un serveur Node.js (SSR, pour « Server-Side Rendering » ou « rendu côté serveur » en français), soit de façon statique en préparant d'avance tous les fichiers (SSG, pour « Static Site Generation » ou « génération de site statique » en français).

Il se démarque néanmoins par de profondes différences que nous allons voir ici en détail. Je pense que les deux principales sont :

  • Pouvoir utiliser n'importe quel framework JS tels que React, Vue.js, etc. Vous pouvez presque tous les utiliser et même plusieurs si nécessaire.
  • Proposer une vraie navigation par page plutôt que simuler une navigation côté client, nous reviendrons largement sur cette idée.

Bizarrement, pouvoir choisir son framework JS est assez incroyable d'un point de vue technique, mais ce n'est pas forcément un critère très important. Rares sont les projets susceptibles de changer de framework ou d'en utiliser plusieurs. Mais ça reste agréable d'avoir le choix.

D'après moi, le vrai facteur différenciant, c'est le multi-pages, un retour aux sources salvateur... Dans certains cas.

Je travaille chez Proton et nous avons réalisé une migration de tous nos sites publiques principaux de Gatsby vers Astro, ce qui nous a donné l'occasion d'approfondir la technologie et d'en avoir une vision assez complète. Je vous propose une présentation générale de la technologie dans un premier temps. Puis je partagerai notre retour d'expérience chez Proton.

Un framework JS multi-pages

Je ne sais pas vous, mais personnellement, je ne change ou n'adopte pas une nouvelle technologie parce que les gens sont sympas, que le site est joli ou que les performances sont meilleures. Pour moi, le plus important, c'est la proposition que fait la technologie que vous considérez. La proposition qui est faite et que les autres n'ont pas. Il y en a presque toujours une.

Pour Astro, bizarrement peut-être, c'est de revenir au multi-pages.

Pour bien comprendre de quoi il s'agit, j'ai été tenté de vous servir tout l'historique depuis le premier serveur HTTP statique mais ça prendrait des heures et beaucoup d'autres l'ont fait.3

Je vais plutôt me contenter de comparer avec ce que ferait Next.js aujourd'hui. Pourquoi Next.js et pas un autre ? Parce que c'est objectivement le framework JS le plus populaire aujourd'hui. Mais vous pouvez le remplacer par Nuxt ou n'importe quel autre, ça marche aussi.

Applications mono-page (Single Page Applications ou SPA)

Ce sera une évidence pour certains mais pas forcément pour tout le monde, dans les frameworks modernes type Next.js, vous ne faites plus un site web avec plusieurs pages mais vous faites une application qui fait semblant de se comporter comme un site web.

Lorsqu'on arrive sur la page, on reçoit d'abord une page statique qui affiche le site proprement mais dont aucune fonctionnalité n'est active. Ci-dessous, les composants inactifs sont représentés en blanc.

Schéma d'une page web avec ses composants de base : entête, menu, contenu et pied de page, tous en blanc

Ensuite, le navigateur va charger le JavaScript de la page. C'est là que l'on devra exécuter un mécanisme mal connu, à savoir l'hydratation. React va exécuter le code de l'application en entier pour rendre la page en mémoire puis comparer le résultat avec la version reçue dans le document HTML, et tout comparer. S'il trouve la moindre différence, il y aura erreur d'hydratation et c'est là que les choses deviennent compliquées. Mais si tout correspond, React est alors capable de reprendre la page et de la rendre interactive, représentée ci-dessous en rouge.

Schéma d'une page web avec ses composants de base : entête, menu, contenu et pied de page, tous en rouge

Cela signifie qu'il faut charger au moins tout le code nécessaire pour calculer la page et jouer une exécution complète alors, que la page est déjà affichée. C'est cette étape d'hydratation qui est coûteuse et parfois compliquée. Tous les frameworks, Vue.js, Svelte… utilisent cette stratégie à l'exception peut-être de Qwik.

Il est ensuite important de parler de la seconde page pour pouvoir faire la différence avec Astro. Dans un framework comme Next.js, lorsqu'on va cliquer sur un lien pour charger une autre page du site, il n'y aura pas de navigation à proprement parler. Le framework va intercepter le clic, charger les données et le code source de la page cible (si nécessaire), et ensuite effectuer les modifications nécessaires à l'affichage de la page. Il utilisera également une API du navigateur pour changer l'URL et créer une entrée dans l'historique de navigation... pour faire comme si. Le navigateur met alors à jour uniquement la zone de la page nécessaire, représentée en bleu ci-dessous.

Schéma d'une page web avec ses composants de base : entête, menu et pied de page en rouge ; la partie contenu est en bleu

Attention, l'idée n'est pas de dire que cette stratégie est mauvaise. Elle a juste des avantages et des inconvénients. Déjà, c'est un peu plus compliqué que juste des pages. Il faut comprendre les notions d'hydratation et de manipulation de l'historique de navigation. Ensuite, il ne faut pas croire que tout le site est chargé à la première page. De nos jours, le contenu des pages ainsi que le code source est chargé à la demande (ce qu'on appelle lazy loading, ou chargement différé en français).

Ce qui fait que si tout le visuel change d'une page à la suivante, la navigation n'est pas tellement meilleure qu'une navigation traditionnelle.

Par contre, bien sûr, pour certaines applications, comme le e-commerce, où toute la structure du site restera en place juste pour changer le produit affiché par exemple, cette approche apporte beaucoup. On charge moins de choses entre deux pages, on ne change que le nécessaire à l'écran et la navigation peut être visiblement plus fluide.

Applications multi-pages (Multi Page Applications ou MPA)

Astro est, à ma connaissance, le seul framework JS ayant fait le choix audacieux de ne pas suivre cette architecture et de proposer une structure plus traditionnelle avec simplement des pages différentes et avec des navigations naturelles d'une page à l'autre.

Astro propose un langage de templating4 côté serveur (nous y reviendrons) qui va gérer la structure de la page. Le framework, React ou un autre, n'est donc pas en charge de la base de page (balises html, head, body...). Pour nous permettre de facilement utiliser le framework de notre choix, ils proposent la solution baptisée « îles Astro » (Astro islands en anglais). Elle représente l'idée qu'à chaque endroit où il y a besoin d'interactivité, on peut créer une « île » gérée par un framework JS. Mais pas toute la page, juste là où il y en a besoin. Par exemple dans l'illustration ci-dessous, l'entête et le contenu (en rouge) sont des îles pour que leur contenu puisse être mis à jour dynamiquement, tandis que le menu et le pied de page (en blanc) sont totalement statiques.

Schéma d'une page web avec ses composants de base : entête et contenu sont en rouge ; menu et pied de page sont en blanc

Chaque île sera donc un mini React avec les mêmes concepts que pour une application entière (comme Next.js) et les mêmes besoins : JS, hydratation... Mais l'avantage est alors de pouvoir « charger le code client » uniquement pour les composants qui en ont besoin. Et la force d'Astro est de rendre cela très facile d'utilisation. Nous y reviendrons plus en détail un peu plus tard.

On voit rapidement qu'Astro se distinguera principalement sur certains types de sites et moins sur d'autres. Sur les sites dit de « contenu », des pages marketing, où le temps d'affichage et le SEO comptent beaucoup, Astro brillera particulièrement. Pour un tel site, avec simplement un menu dynamique implémenté avec un framework léger comme Svelte ou Preact, le total de JavaScript peut facilement être inférieur au seul poids de React.

Mais plus il s'agit d'une application riche avec beaucoup de code, moins son intérêt sera flagrant.

Il ne faut pas oublier la contrepartie : pour aller consulter la seconde page, on fera une vraie navigation. On ne bénéficiera plus de la navigation côté client qui avait tout de même de réels avantages, notamment un ressenti de navigation très fluide.

Par contre, et c'est là qu'on a l'impression de revenir aux sources, on va vite redécouvrir des fonctionnalités de navigateur un peu oubliées. Au lieu d'avoir à simuler tous les comportements naturels d'un navigateur, tout devient plus simple : un lien est un lien, l'historique fonctionne tout seul. Précharger la page suivante avant de cliquer sur le lien est possible, mettre en cache un fichier utilisé sur toutes les pages, c'est facile… À croire que les navigateurs sont faits pour ça !

Avantages et inconvénients

En mettant à plat l'élément central que sont les îles Astro, j'ai déjà évoqué certains avantages et inconvénients, mais essayons de les rassembler ici.

Pour les utilisateurs et utilisatrices

  • Avantages
    • Moins de JavaScript
    • Moins d'hydratation
  • Inconvénients
    • Rechargement de toute la page lors des navigations

Pour les développeurs et développeuses

  • Avantages
    • Gestion simple de la navigation entre les pages, à base de liens
    • Choix du framework
  • Inconvénients
    • Nouveaux templates Astro à apprendre
    • Plusieurs contextes dans la page

Les bases d'Astro

Sans faire un tutoriel de mise en œuvre, je pense qu'il est intéressant de faire le tour des éléments de bases qui structurent la façon de travailler avec Astro.

Vite

Logo de Vite

La première chose à savoir à propos d'Astro, c'est qu'il est construit par-dessus Vite. Ils sont vraiment intimement liés. Vite implémente les modes SSR et SSG d'Astro, ainsi que le mode de développement. On verra qu'Astro ajoute la notion d'adaptateur (adapter en anglais), permettant de déployer facilement sur la plupart des plateformes modernes, mais la mécanique de base provient intégralement de Vite.

Pour s'en convaincre, il suffit de comparer un fichier de configuration pour Vite, ici avec SvelteKit :

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';

export default defineConfig({
    plugins: [sveltekit()],
    test: {
        include: ['src/**/*.{test,spec}.{js,ts}']
    }
});

Et un fichier de configuration Astro :

import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import ssrAdapter from '@protonme/astro-ssr-adapter';
import wpData from '@protonme/blog/logic/wordpress/wpDataIntegration';
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
    site: 'https://proton.me/',
    base: 'blog',
    output: 'server',
    integrations: [
        react(),
        tailwind({ path: './tailwind.config.cjs', applyBaseStyles: false }),
        wpData('proton-me'),
    ],
    adapter: ssrAdapter(),
    vite: {
        optimizeDeps: { exclude: ['msw'] },
    },
});

Mais loin de moi l'idée de dire que c'est une mauvaise idée. L'héritage clair et l'accès direct à la configuration de Vite permet de bénéficier de tous les outils de la communauté.

Mais aussi, et plus simplement, Vite est une technologie moderne et enthousiasmante. Elle prend la place d'un webpack vieillissant et d'un Turbopack tardant à le remplacer avantageusement. Il est construit sur deux technologies principales que sont esbuild pour le mode « dev » (étape de développement en local de l'application) et Rollup pour le mode « build » (phase de génération de l'application avant son déploiement sur un serveur). Ce sont deux outils fondamentaux bien plus modernes et performants que webpack.

Il y aurait beaucoup à dire sur Vite mais il faudrait un article à part entière. Je me contenterai de dire que c'est un net progrès en venant de webpack.

Modes de rendu (outputs en anglais)

Astro (à travers Vite) propose trois modes de base pour spécifier la cible de sortie de l'application générée. Il suffit de choisir avec le paramètre output dans le fichier de configuration :

output: 'static'

C'est le mode de génération statique, toutes les pages seront calculées à la commande build. Pour les routes dynamiques, il faudra définir une fonction getStaticPaths() pour lister toutes les valeurs possibles. La génération produira un site fini dans le répertoire /dist.

output: 'server'

C'est le mode serveur, toutes les pages seront calculées à la demande. Seules les ressources du site seront générées. Il vous faudra par contre un serveur Node.js pour faire tourner le site. Lors de la génération seront produits un répertoire /dist/client pour les ressources statiques et un repertoire /dist/server pour le code du serveur.

output: 'hybride'

Le mode hybride est le plus intéressant parce qu'il est moins courant dans ce genre de technologie. Il propose, comme son nom l'indique, d'utiliser les deux solutions précédentes à la fois. Vous aurez besoin d'un serveur pour faire tourner le site mais pourrez aussi précalculer vos pages les plus courantes pour optimiser les ressources et améliorer les performances à l'exécution.

L'utilisation de ce mode est assez simple, il suffit d'ajouter cette ligne à une page que l'ont veut précalculer:

export const prerender = true;

Fichiers .astro

Une des marques de fabrique d'Astro est un nouveau format de fichier qui lui est propre : les fichiers .astro. Ce ne sont ni plus ni moins que des template côté serveur. Retenez bien cette notion : les fichiers .astro sont des fichiers exclusivement côté serveur. Comme ils ont une syntaxe moderne « à la React », on fait vite l'erreur de l'oublier.

« Zut alors, il faut que j'apprenne un nouveau language ». Rassurez-vous, c'est très facile d'accès. En effet, il ne s'agit au final que d'un fichier TypeScript en haut avec une partie JSX5 en-dessous. Pour la plupart des gens qui ont fait un peu de React, il n'y aura vraiment rien de nouveau. Le seul truc un peu pénible, c'est la configuration des éditeurs et autres CI pour analyser ce nouveau format…

---
const nom = "Astro";
const animaux = ["Chien", "Chat", "Ornithorynque"];
const visible = true;
---

<div>
  <h1 class={name}>Bonjour {nom}!</h1>
  <ul>{animaux.map((animal) => <li>{animal}</li>)}</ul>
  {visible && <p>Montre moi !</p>}
  {visible ? <p>Montre moi !</p> : <p>Sinon montre moi !</p>}
</div>

…et les outils de coloration syntaxique dans les articles de blog !

Routeur

Astro embarque un routeur assez standard. Ainsi que la mode le veut, il est « basé sur les fichiers », c'est à dire que le routage de base se fera en fonction de l'emplacement et du nom des fichiers localisés dans le répertoire src/pages.

Ces fichiers sont soit des fichiers .astro qui donneront des pages, soit des fichiers .ts ou .js qui donneront les points de terminaison (« endpoints » en anglais). Nous reverrons ces notions juste après.

Les noms de fichier peuvent contenir des noms de variables avec la syntaxe [var].astro pour capturer facilement un paramètre dans l'URL, ou même plusieurs avec les trois points [...vars].astro.

Vous n'aimez pas vraiment les routeurs par système de fichier ou ça ne correspond tout simplement pas à votre besoin ? Ça arrive, ça m'est arrivé... Créez simplement un fichier src/pages/[...params].astro et il recevra à peu près toutes les requêtes de votre site. À vous de faire votre routage avec du code comme bon vous semble.

Les îles Astro

Mais Astro ne se limite pas à Vite avec un nouveau language de template qui ressemble à React, ce serait un peu léger. La touche de magie qui fait décoller la fusée, à mon avis, ce sont les îles Astro. Un nom packagé et marketé pour décrire une API permettant, à partir de fichiers .astro, de prendre n'importe quel composant d'un framework JS et de l'utiliser très simplement.

Son Goku dans sa forme normale, avec les cheveux noirs

Commençons doucement avec un composant React pour prendre un exemple. On peut mettre un composant React là, comme ça, au milieu d'un template côté serveur, comme si de rien n'était.

---
import { MyReactComponent } from ‘./MyReactComponent.ts’; 
---
<MyReactComponent />

Et Astro va se débrouiller pour faire fonctionner React, le rendu côté serveur, et insérer ce composant là où vous le demandez.

On reviendra sur la configuration mais si vous vous dites qu'il faut des pages de configuration pour faire ça, sachez que la seule section integrations: [react()] du fichier de configuration Astro, présenté plus haut, est suffisante pour cet exemple.

Son Goku dans sa forme Super Saiyan niveau un, avec les cheveux jaunes

Avec cet exemple, votre code reste purement côté serveur, aucun code JavaScript n'est chargé côté client. Génial, vous avez fait un site avec zéro JS, super rapide ! Bon, de fait, il est totalement inerte du coup également. C'est un peu la logique d'Astro. Par défaut, pas de JavaScript côté client, le moins possible, seulement si nécessaire.

Maintenant, admettons que ce composant React ait besoin d'interactivité. Comment faire ? Simplement une directive client:load en plus et vous pouvez dire à Astro de charger le JavaScript de votre composant côté client.

---
import { MyReactComponent } from ‘./MyReactComponent.ts’;
---
<MyReactComponent client:load />

Rendu côté serveur, hydratation, tout est pris en charge. Un mini React est démarré pour vous au niveau de ce composant précisément.

Son Goku dans sa forme Super Saiyan niveau trois, avec les cheveux jaunes et longs

Et tant qu'à faire, pourquoi ne pas aller plus loin et démarrer les composants en fonctions de critères plus fins ?

---
import { MyReactComponent } from ‘./MyReactComponent.ts’;
---
<MyReactComponent client:idle={{timeout: 500}}/>

Astro propose toute une collection de directives client (en anglais « client directives ») permettant de contrôler précisément si et quand charger le JavaScript de chaque composant.6

Son Goku dans sa forme Super Saiyan dieu, avec les cheveux rouges

Et si on peut charger un composant React, pourquoi pas d'autres également ? Et en même temps tant qu'on y est ? Et bien oui, on peut, presque tous les frameworks, et même potentiellement tous en même temps.

---
import { MyReactComponent } from ‘./MyReactComponent.ts’;
import MySvelteComponent from './MySvelteComponent.svelte';
---
<MyReactComponent />
<MySvelteComponent />
Son Goku dans sa forme Super Saiyan bleu, avec les cheveux bleus

Dans les faits, ce n’est pas ce qui est pratiqué communément. Charger plusieurs frameworks en même temps additionne le coût en JavaScript et, souvent, la complexité technique. Sans parler de la gestion des compétences dans l'équipe.

Mais cela peut répondre à quelques besoins précis, comme un site majoritairement avec Svelte mais utilisant React sur une page en particulier, parce qu'un composant spécifique incontournable a besoin de React.

Points de terminaison

Clairement moins « waouh » que les îles, les points de terminaison font partie de ces fonctionnalités simples et pragmatiques qui font plaisir. C'est le genre de fonctionnalités qui a tendance à me faire penser qu'une technologie a été bien pensée. Il y a des solutions proposées face à des besoins.

Quand vous n'avez pas une page à faire mais un truc bizarre tels qu'un flux RSS, un JSON dynamique, un fichier XML, n'importe quoi… plutôt que d'utiliser un fichier .astro inadapté, vous pouvez créer simplement un fichier .ts.

Vous obtenez une API simple avec les méthodes HTTP auxquelles vous pouvez répondre avec le contenu que vous voulez. Cela fonctionne avec le même routeur basé sur le nom des fichiers. L'exemple ci-dessous crée une route qui retourne un contenu dynamique pour un fichier JSON.

// Fichier : /src/pages/builtwith.json.ts
// URL : /builtwith.json
export async function GET({params, request}) {
  return new Response(
    JSON.stringify({
      name: 'Astro',
      url: 'https://astro.build/'
    })
  )
}

Fonctionnalités avancées

Les fonctionnalités de base, les îles, le routeur, les fichiers .astro… sont difficiles à éviter. Si vous faites un site avec Astro, vous verrez tout cela. Mais Astro propose aussi des fonctionnalités plus avancées que, soit vous utiliserez sans vous en rendre compte, soit vous n'utiliserez pas nécessairement.

Je ne les ai pas forcément toutes utilisées moi-même, certaines oui, certaines pour essayer, certaines non. Je vais tout de même essayer de vous les présenter pour vous dessiner un panorama des possibles.

Middleware

Commençons doucement, j'ai hésité à mettre celle-ci dans les fonctionnalités de base car nous l'avons beaucoup utilisée chez Proton. C'est bien pratique pour tout un tas d'utilisations.

Il s'agit du concept de middleware7 bien connu des utilisateurs d'Express ou de la plupart des serveurs web Node.js. L'API et le fonctionnement sont presque identiques à Express. Vous implémentez une fonction onRequest qui vous permet d'ajouter du code avant ou après le traitement des pages.

Astro ajoute une API Astro.locals qui permet de faire le lien facilement avec vos pages. Ainsi, vous pouvez préparer des choses, charger des données et les récupérer facilement depuis votre page. Attention, c'est pratique mais ce n'est pas recommandé pour charger les données d'un CMS par exemple.

Chez Proton, nous l'avons utilisé pour plusieurs besoins : du monitoring, des systèmes de redirections configurables, de cache ou encore d'internationalisation.

Voici un exemple simple de middleware.

// intercepte les requêtes
export function onRequest (context, next) {
    // possibilité de modifier des propriétés dans `Astro.locals`
    context.locals.title = "New title";

    // retournez soit une réponse, ou le résultat de la page en appelant `next()`
    return next();
};

Intégrations

Les intégrations8, c'est le gros morceau d'Astro. C'est plus ou moins le point d'ouverture d'Astro voire plus généralement de Vite pour faire à peu près n'importe quoi.

Vous vous en servez naturellement. Le plugin Astro pour React, ou quel que soit le framework que vous voulez utiliser, est une intégration. Le plugin pour Tailwind aussi, etc...

En fait, ça va même plus loin. Cela ne serait pas entièrement faux de dire qu'Astro est lui-même une intégration sur Vite. Vite c'est puissant, je vous le disais plus haut dans cet article. Et je ne suis pas le seul, les créateurs de Nuxt, Remix, SvelteKit et autre SolidStart sont du même avis.

Techniquement, cela fonctionne avec un système de hook9. Une intégration peut implémenter toute une liste de hooks qu'Astro appellera à différentes étapes de son cycle de vie. Il vous passera des paramètres qui vous permettront d'intervenir sur le comportement et d'ajouter des fonctionnalités.

interface AstroIntegration {
  name: string;
  hooks: {
    'astro:config:setup'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:config:done'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:route:setup'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:server:setup'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:server:start'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:server:done'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:build:start'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:build:setup'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:build:generated'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:build:ssr'?: (options: { /* ... */ }) => void | Promise<void>;
    'astro:build:done'?: (options: { /* ... */ }) => void | Promise<void>;
    // ... possibilité d'avoir d'autres hooks provenant d'autres d'intégration
  };
}

Commencer à s'immiscer dans le cycle de vie d'Astro / Vite est très puissant mais aussi assez complexe. Chez Proton, nous ne l'avons utilisé qu'une fois pour ajouter un comportement à la fin de la génération, donc seulement pour un besoin assez basique.

Adaptateurs

Dans les faits, les adaptateurs10 ne sont rien d'autre que des intégrations. Pour les puristes, ils ne devraient peut-être pas être identifiés comme quelque chose d'autre. Pourtant ils le sont dans la documentation d'Astro et je pense que cela a du sens.

Les adaptateurs sont des intégrations spéciales dédiées au déploiement, à l'hébergement et à l'exécution d'Astro pour tous les environnements existants. Il faut avouer que faire tourner de façon optimisée une application web sur Vercel ou Cloudflare, en tirant parti de toutes les fonctionnalités d'edge computing11, est devenu assez compliqué. Avec cette notion d'adaptateur, il est possible de publier une extension qui définira clairement comment publier proprement une application Astro (sur Cloudflare par exemple) au mieux.

D'ailleurs, chez Proton, on en a écrit un pour chez nous. Il n'est pas open source, et ça n'apporterait vraiment rien puisque c'est en relation direct avec notre infrastructure. Mais nous avons notre propre adaptateur qui nous permet de publier un site Astro sur notre infrastructure, et c'est bien pratique !

Content layer

On arrive aux fonctionnalités que nous n'avons pas mises en œuvre. La premiere, pour une très bonne raison (elle n'était pas encore sortie, ou pas vraiment, pendant nos développements) : le content layer (qu'on peut traduire par « couche de contenu » en français.

Depuis le début d'Astro, les concepteurices ont prévu une API pour gérer des données statiques, spécialement pensées pour les sites alimentés par fichiers Markdown dans un répertoire src/content.

Dans les grandes lignes, vous mettez du contenu dans le bon répertoire, vous définissez une structure avec la librairie Zod et enfin, depuis vos pages, vous pouvez accédez à vos données avec une API getCollection et getEntry.

Le problème de cette implémentation était qu'elle n'était pas extensible. Avec la version 5 d'Astro, la fonctionnalité principale mise en avant est un nouveau content layer. D'après la documentation que j'ai pu lire, ils ont repris les idées de cette API mais l'ont généralisée.

On peut maintenant implémenter n'importe quelle source de données sur ce schéma, y compris des données asynchrones. Une API est définie pour définir le typage des données avec Zod, comment récupérer les données depuis un CMS par exemple. Leur utilisation se fait ensuite avec l'API habituelle.

À ceci s'ajoute que des éditeurs et éditrices de CMS ont la possibilité de publier leur propres extensions pour que cette integration ne soit même plus à faire. Ils ont déjà annoncé Storyblok, Cloudinary, Hygraph, Strapi, Notion...

Pour nous chez Proton, sortant de Gatsby, une des choses qui nous a marqués était la complexité de devoir travailler avec le modèle de données internes de Gatsby, que nous avons trouvé vraiment trop compliqué (n'entrons pas dans le débat GraphQL ici !). Avec ce content layer, même si cela paraît bien plus simple que Gatsby, Astro apporte un modèle de données interne et nous nous demandons si c'est une bonne idée. Ou alors nous cherchons juste à justifier le fait d'avoir tout fait à la main 😀, mais de toute façon, cela n'existait pas au moment où nous le faisions.

Transitions de vue

Les transitions de vue (« view transitions » en anglais)12, c'est un peu la fonctionnalité « wahou » d'Astro. Ça fait de belles démos mais je ne suis pas tout à fait sûr que beaucoup de monde la mettra en production... En tout cas, nous ne l'avons pas fait chez Proton.

Sur le papier pourtant, il s'agit du chaînon manquant pour qu'un site multi-pages se comporte complètement comme un site mono-page. Cela s'appuie sur une nouvelle API navigateur, seulement supportée par Chromium à l'heure où ces lignes sont écrites malheureusement (70% des utilisateurices). Cette API permet de créer des animations entre la page de sortie et la page d'entrée. Cela fonctionne avec un mécanisme complexe de chargement de la page suivante en arrière-plan et un passage transparent d'une page à l'autre.

Comme souvent, l'API native du navigateur n'est pas très sympathique à utiliser, sans parler de la gestion de la compatibilité entre les navigateurs, qui dans ce cas, sont sérieuses. Astro propose une intégration très poussée de cette nouvelle fonctionnalité pour nous en faire profiter beaucoup plus facilement. Le plus bel exemple étant ce point d'entrée assez plaisant.

---
import { ViewTransitions } from 'astro:transitions';
---
<html lang="en">
  <head>
    <title>Mon site</title>
    <ViewTransitions />
  </head>
  <body>
    <h1>Bienvenu sur mon site !</h1>
  </body>
</html>

Je l'ai testé, cela marche plutôt bien. Quand on creuse un peu, avoir ce chargement de la page suivante en arrière-plan est puissant.

Par contre, la partie plus discutable est de savoir si on a vraiment besoin ou envie d'animations entre les pages ? Alors oui, ça fait « wahou » en conférence mais vous connaissez beaucoup de sites qui ont ça ? Pas tant que ça, tout simplement parce que ça alourdit beaucoup et ça n'apporte pas grand chose. Et si, à cause de ce besoin finalement assez limité, Firefox et Safari n'implémentaient pas la spécification, peut-être que la technologie va simplement mourir. Je ne crie pas au génie ni ne l'enterre, mais j'attends de voir.

Actions

Les actions Astro (ou « server actions en anglais) représentent la fonctionnalité proposée (récemment, depuis la version 4.15) pour implémenter des requêtes serveur. Vous pourriez utiliser un point de terminaison, mais ici, l'idée est d'aller un peu plus loin avec une fonctionnalité aboutie depuis l'appel client jusqu'à la validation des paramètres.

Sans paraphraser toute la documentation, les clés principales sont une déclaration dans le répertoire src/actions.

import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  getGreeting: defineAction({
    input: z.object({ name: z.string() }),
    handler: async (input) => {
      return `Salut, ${input.name}!`
    }
  })
}

Et son utilisation depuis les pages avec un import de astro:actions

<script>
import { actions } from 'astro:actions';

const button = document.querySelector('button');
button?.addEventListener('click', async () => {
  // affiche un message de bienvenue provenant de l'action
  const { data, error } = await actions.getGreeting({ name: "Houston" });
  if (!error) alert(data);
})
</script>

Encore et encore...

Et bien sr, tout cela n'est qu'une sélection des fonctionnalités existantes, je ne pouvais pas parler de tout. J'en citerais encore trois rapidement.

Les Server Islands qui arrivent dans la version 5 (en beta à l'heure de l'écriture) ont une utilisation assez marginale. Cela correspondrait à des composants uniquement rendus côté serveur mais très lourds, dont on veut retarder le chargement par rapport au reste de la page. Mais ce qui est intéressant sur le long terme, c'est qu'on a l'impression qu'iels sont en train de poser les bases pour de potentiels composants serveurs « à la » React Server Component. Et ça, il faut avouer que ça pourrait être drôlement bien.

Starlight est la surcouche à Astro, faite par Astro, pour faire un site de documentation. On pourrait dire que ce n'est jamais qu'un template. Mais pour l'avoir utilisé pour notre documentation interne, c'est quand même très appréciable : pas de HTML et CSS dans les sources, juste du Markdown, tout est préconfiguré, c'est juste pratique.

Astro DB est probablement la partie que je maîtrise le moins bien. Astro propose carrément une base de données intégrée. Comme les autres fonctionnalités d'Astro, vous allez dans le répertoire qui va bien, vous lancez un petit import magique import { defineDb } from 'astro:db'; et c'est parti. L'API est basée sur SQL donc ça ne devrait pas trop vous déstabiliser. N'y mettez pas la comptabilité de votre société bien sûr, mais ça peut rendre service je pense.

Astro chez Proton

Chez Proton, au cours de l'année passée, nous avons migré nos principaux sites proton.me et protonvpn.com de Gatsby vers Astro. En réalisant cette migration, j'imaginais déjà cet article comme le retour d'experience parfait : de tout l'un à tout l'autre, les bénéfices et les difficultés rencontrées.

Je me rends compte aujourd'hui comme j'étais naïf. Un tel retour d'expérience n'existe pas dans le monde réel. Tant de choses ont changé dans l'entreprise, dans l'équipe, dans les contraintes techniques et même dans notre façon d'aborder le projet… Ce que nous avons en production aujourd'hui avec Astro n'est pas vraiment comparable avec ce qu'il y avait avant. Donc ce n'est clairement pas 100% Gatsby versus Astro.

Mais ça reste ce que j'ai de plus proche à vous proposer : deux gros sites utilisant Gatsby il y a deux ans et qui sont sur Astro aujourd'hui.

Contexte

La situation de départ était un site proton.me construit avec Gatsby en une génération unique. Environ 200 pages marketing configurées dans le CMS headless13 Prismic. Auxquelles s'ajoutaient environ 700 articles de blog et 500 articles de support dans un WordPress, aussi utilisé en headless. On arrive à pas loin de 1 500 pages qu'on multiplie par une dizaine de langues pour l'internationalisation. Chaque génération produisait environ 15 000 pages et 4 Go de données. Pour faire ça, il fallait un exécuteur (« runner » en anglais) de CI spécial avec 8 Go de RAM (juste pour que ça ne crashe pas) et une heure en tout pour la pipeline.

Et ça, pour changer la moindre virgule sur le site...

Le site protonvpn.com était un peu différent, mais c'était le même genre de plaisir. Le tout dans un répertoire différent, rendant difficile de partager du code entre les deux sites. Autant dire que c'était compliqué et que les crispations étaient nombreuses et plutôt justifiées.

Gatsby fut assez vite identifié comme une cause de nos soucis et l'envie très forte de « refaire » est venue très vite. Mais on voit bien dans ce schéma que si Gatsby a ses torts, c'est le concept même de génération de site statique qui n'était pas tenable à l'échelle. Et ça, ce n'est pas que nous qui le disons. Regardez l'écosystème, tout le monde s'est tourné vers les CDN et le cache, la génération statique n'a plus vraiment la côte.

Après d'interminables négociations en interne, nous avons obtenu l'accord pour un changement d'outillage technique. Mais tout en sachant qu'Astro en lui-même ne serait pas la solution à tous les problèmes.

Migration

On aimerait tous et toutes faire une migration en arrêtant tout, en cassant tout et en prenant le temps. Et bien sûr, on ne peut pas. Nous avons pensé monter un monorepo14 dans lequel le code des sites serait dans des packages. Pendant ce temps, nous monterions le nouveau site avec Astro, en parallèle de l'ancien toujours en production. Et c'est exactement ce que nous avons fait.

Nous avons d'abord transformé le dépôt git en monorepo avec pnpm et Turborepo. Une fois le monorepo en place, nous avons pu accueillir le site de Proton VPN et commencer à partager plus de code.

Pour que nos composants React soient utilisables à la fois dans Gatsby et Astro, il fallait aussi que certains composants stratégiques soient compatibles. C'était le cas principalement pour les composants Link et Image (gérant respectivement l'affichage des liens et des images). Ça a été une étape assez compliquée, et surtout invisible et propice aux régressions désagréables.

Ensuite, nous avons pu « extraire le blog » : extraire le code du monolithe dans son propre package. Préparer un nouveau blog avec Astro, mais en dynamique cette fois-ci, fini la génération de 10 000 pages ! Ça n'a pas été facile, passer au dynamique, à la consommation mémoire, au monitoring, au cache, à l'invalidation de cache… c'est un métier. Mais nous ne regrettons rien, c'était simplement nécessaire.

Seulement ensuite, nous nous sommes attaqués à la racine du site et nous avons choisi de garder la génération statique. Le site est beaucoup plus sensible, le trafic bien supérieur et le nombre de pages bien plus limité.

Je vous épargne nombres de sujets annexes comme l'hébergement des images sur Cloudinary, les problèmes d'hébergement de WordPress, la migration de l'infrastructure vers Kubernetes

Résultats

Par où commencer ? C'est tellement de travail, pour nous autres développeurs et développeuses, je pense que c'est d'abord des bases saines et un environnement de développement moderne et solide pour la suite.

Mais pour un site marketing, même si ça nous paraît réducteur, on rapportera probablement comme premier chiffre la performance Lighthouse. Alors oui, je vous rassure, nous avons bien progressé. D'environ 70 à 80 en score de performance mobile et de 90 à 95 sur ordinateur.

Tant qu'on est dans les chiffres, faisons un tour complet :

  • Le TBT (Total Blocking Time15) est passé de 2 secondes à 1 seconde sur mobile.
  • Le poids moyen de JavaScript par page est passé de 450 Ko à 285 Ko (c'est encore trop, j'y reviendrai).
  • Le temps moyen d'une pipeline de la CI est passé de 1 heure à 15 minutes (et je n'ai pas dit mon dernier mot).
  • Le démarrage d'un serveur de développement est passé de 5 minutes à 5 secondes. 😀
  • Le temps de publication d'un article de blog est passé de 2 heures à 1 minute.
  • Le temps de publication d'une page marketing est passé de 2 heures à 20 minutes.

En bout de course, c'est une réussite, les chiffres sont bons, l'expérience de développement a été plaisante. Nous avons livré dans les temps, tout le monde est plutôt content.

Je ne sais pas ce que vous en pensez, mais à l'usage, on repère de manière évidente les technologies qui font plaisir et celles qui sont une contraintes. Astro fait clairement partie de la première catégorie. Tout n'est pas parfait mais on prend plaisir à l'utiliser et, la plupart du temps, on trouve une solution pour faire ce qu'on veut faire plutôt proprement.

Il reste de la marge

Un dernier point que je voudrais aborder avant de conclure est la marge de progression que nous apercevons encore devant nous pour optimiser le site.

Vous l'aurez compris, la grande majorité du code des deux sites reste écrite et « pensée » en React. Ces composants ont seulement été projetés dans des îles Astro. Pour chaque île, il nous a fallu choisir « est-ce qu'on charge le code côté client ou pas ? ». Pour un site marketing avec de nombreuses zones de texte et d'illustrations, j'espérais naïvement pouvoir répondre « non » le plus souvent. Malheureusement, ce n'est pas si facile.

Un exemple que j'espère parlant : partout où il y a un lien sur le site, il est possible, en fonction du CMS, que ce lien pointe vers le téléchargement d'une de nos applications et cela déclenche de la logique côté client. Du coup, les liens demandent du code côté client… peut-être. Toutes les sections du site qui contiennent potentiellement un lien ont besoin de code côté client… Et boum, presque tout le site charge son JS côté client. Pas très Astro…

Il y a des solutions à ça : faire plus de .astro et moins de React, ou découper les parties cliente et serveur en React pour pouvoir charger certaines parties et pas d'autres. Mais comme le site n'a pas été pensé pour Astro dès le début, ce n'est pas encore le cas. Cela prendra un peu plus de temps, nous avons déjà gagné beaucoup et le côté positif est de se dire qu'il est possible d'aller encore plus loin.

Conclusion

Après une solide mise en œuvre d'Astro, la technologie est enthousiasmante. Les concepts sont clairs et leur mise en œuvre simple et pratique. L'utilisation est agréable et les performances sont au rendez-vous, aussi bien pour les utilisatrices et utilisateurs finaux que pour les développeurs et développeuses.

Si j'osais, j'ouvrirais même cette conclusion en demandant si Astro ne pourrait pas devenir une forme de standard au-dessus de Vite ?

Si on suppose que Vite est déjà en train de devenir un standard pour la génération d'application web, avec lequel il est facile de mettre en œuvre tous les outils de développement habituels, Astro ajoute la surcouche qui le transforme en serveur web facile à deployer et dans lequel on peut littéralement parachuter n'importe lequel de nos frameworks web JS, sans presque aucune configuration pour que tout fonctionne, avec rendu côté serveur et hydratation.


  1. Voir la définition de framework sur Wikipédia

  2. Pour en savoir plus sur les différences entre JavaScript et Typescript, vous pouvez lire « Du JavaScript à TypeScript : transformez votre approche du front-end » d'Aline Leroy, publié sur 24 jours de web en 2023. 

  3. Par exemple dans cette vidéo YouTube « CDN Caching, Static Site Generation, and Server Side Rendering » (en anglais). 

  4. Anglicisme souvent employé pour parler d'un modèle de document - encore appelé en français gabarit ou squelette de document. 

  5. Le JSX est une extension React de la syntaxe du langage JavaScript. Voir JSX sur Wikipédia

  6. Documentation d’Astro sur les directives client 

  7. Traduit par « intergiciel » en français. Voir middleware sur Wikipédia ; Documentation d’Astro sur middleware

  8. Documentation d'Astro sur les intégrations

  9. Traduit par « crochet » en français. Voir la définition de hook sur Wikipédia

  10. Documentation d'Astro sur les adaptateurs

  11. Traduit par « informatique en périphérie ». Voir la définition d'edge computing sur Wikipédia

  12. Documentation d'Astro sur les transitions de vue

  13. Un CMS headless propose uniquement la gestion des contenus du site, sans s'occuper de la présentation. 

  14. Anglicisme pour décrire un dépôt de code unique contenant plusieurs projets. 

  15. Traduit par « temps de blocage total » en français. En savoir plus sur web.dev

Il n’est plus possible de laisser un commentaire sur les articles mais la discussion continue sur les réseaux sociaux :