Introduction
J'ai voulu faire ce projet de "portfolio + blog", car j'avais l'ambition de revenir à des éléments fondamentaux du développement web. Des connaissances qui seraient toujours bonnes à avoir dans sa boîte à outils de développeur. Depuis une dizaine d'années, les formations en ligne ou dans certaines écoles privées boostées à l'argent public, ont des enseignements très... En surface.
Pourquoi s'embêter d'enseigner comment fonctionne un navigateur ou manipuler le DOM quand les frameworks s'occupent de tout pour vous, avec en plus un fonctionnement plus complexe à cause de l'expansion des SPA (Single Page Application) pour un pauvre site statique ?
L'envie d'apprendre en échouant
J'avais envie de revoir les bases. C'est-à-dire :
Rendre côté serveur des informations côté navigateur, afin que ce dernier fasse ce qu'il sait faire de mieux : afficher des pages, c'est tout.
Faire un site statique, quoi.
De plus, je me suis confronté à pas mal de contre-vérités que j'avais en tête :
-
Pas de javascript, donc pas d'interactions = perf au top
Non, le traitement des images et des assets sont très importants.
-
Je n'ai pas besoin de compresser les données HTTP envoyé au navigateur, je n'envoie que du HTML et du CSS
Si, ça améliore significativement le temps de réponse, réduction de la bande-passante et donc l'efficacité du serveur, entre autres.
-
La minification du css et js ? Ca se fait tout seul au build non ?
-
Ah, mais oui, je dois m'occuper de la transcompilation Typescript vers du Javascript...
Bref, je pensais que ce projet "tranquille" serait simple, mais entre les petites expérimentations pas abouties et la mise en production d'un produit, c'est une autre histoire.
La partie blog : SSG et CMS
En plus d'un site personnel minimaliste, je voulais aussi avoir un côté blog pour mettre à plat mon apprentissage au quotidien, pouvoir prendre du recul sur ce que je fais, ainsi que les outils que j'utilise pour des projets.
CMS (Content Manager System)
Wordpress était directement éliminé : une usine à gaz.
Ghost - une alternative à Wordpress plus légère et minimaliste : même chose au final.
SSG (Static Site Generation)
On arrive maintenant aux générateur de sites statiques, comme : Jekyll, Hugo ou encore Astro, etc.
Si je devais refaire ce projet, je serais certainement parti entre ces trois choix. Ils ont chacun leurs avantages et inconvénients. Astro aurait été le plus familier, je pense.
Gestion du Markdown
Plutôt que d'adopter un SSG tout fait, j'ai voulu gérer par moi-même le parsing
des articles de blog. En effet, chaque article est un fichier .md stocké dans
src/views/content/.
J'utilise deux librairies :
gray-matter pour extraire le frontmatter YAML (titre, date, tags, etc etc.) du fichier Markdown.
markdown-exit avec Shiki pour convertir le contenu en HTML avec coloration syntaxique.
Ci-dessous le service qui lit et parse les articles :
export async function getPosts(files: string[]) {
const posts = await Promise.all(
files.map(async (file) => {
const content = await fs.readFile(path.join(CONTENT_DIR, file), "utf-8");
const { data } = matter(content);
return {
slug: file.replace(".md", ""),
title: data.title,
description: data.description,
date: new Date(data.date),
image: data.image,
tags: Array.isArray(data.tags)
? data.tags
: data.tags
? [data.tags]
: []
};
})
);
return posts;
}
matter(content) sépare le frontmatter du corps de l'article. Le reste, comme
la conversion Markdown vers HTML, est ensuite délégué au moteur de template au
moment du rendu.
C'est une approche minimaliste, sans base de données ni CMS : les articles sont de simples fichiers texte versionnés avec le reste du code.
La stack technique
Architecture du projet
Avant de rentrer dans le détail, je vais présenter la structure générale du projet, qui est une structure en couches afin de séparer au mieux les responsabilités :
src/=> code source en Typescriptservices/=> logique métier avecblog.service.ts,markdown.service.ts,assets.service.ts,data.service.ts,rss.service.tsviews/=> templating avec EJSviews/content/=> Articles en Markdown
middleware/=> variables globales injectées dans Expressrouter.ts=> définitions des routes
public/=> assets statiques (css, js, fonts, images)dist/=> code compilé généré au build, non versionné biensûr
Node.js avec Express
Au final, je suis parti sur une stack technique minimaliste et là où je suis le plus à l'aise. Je suis donc parti sur le runtime Javascript Node.js en utilisant le framework Express, qui permet de construire des serveurs web simplement, et avec le juste nécessaire.
Moteur de template avec EJS
J'ai besoin d'un moteur de template : j'ai choisi ce bon vieux EJS. Il est simple à prendre en main, encore maintenu et facilement intégrable avec Express.
Edgejs, le moteur builtin d'Adonis m'a fait de l'oeil, mais la mise en place était un peu plus laborieux.
EJS me permet tout de même de faire des "composants" avec les partials, j'ai pu découpé certains block comme les cards que j'utilise pour les sections projets et articles.
<article class="card">
<img
class="card-image"
src="/images/<%= post.image %>"
alt="card image"
loading="lazy"
decoding="async"
/>
<div class="card-content">
<a href="/blog/<%= post.slug %>">
<h3 class="card-title"><%= post.title %></h3>
</a>
<p class="card-description"><%= post.description %></p>
<small> <%= post.date %> </small>
<div class="container">
<% for (tag of post.tags) { %>
<a href="/blog/tag/<%= tag %>">
<small class="badge"> <%= tag %> </small>
</a>
<% } %>
</div>
</div>
</article>
Puis on va l'injecter dans notre page :
<section class="section">
<h2>Dernières Publications</h2>
<article class="section items-center">
<% posts.forEach(post => { %> <%- include('../card-article', { post: post })
%> <% }) %>
</article>
<!--[...reste du code]-->
</section>
Style : CSS Natif
Côté style, j'ai choisi de faire du CSS natif et de ne pas utiliser les nombreux CSS-in-JS framework. J'ai commencé le développement en 2023, j'aimerais me mettre à jour sur les nouveautés CSS.
Je trouve que nativement, on peut remplacer pas mal de Javascript qui alourdissent bêtement des applications ou des sites. Autant profiter des évolutions positives, tout en gardant un oeil sur la compatibilité des navigateurs avec celles-ci.
Je voulais aussi aborder rapidement le fait de partir sur un design "mobile-first" pour me faciliter la vie sur le responsive des pages, ainsi que le intrinsic patterns qui permet de se baser sur la taille d'un élément sur son contenu, sans se soucier de son contexte. Ce qui m'a permis d'avoir moins de media queries éparpiller dans ma feuille de style.
Performance et Accessibilité
Cela va de paire, certaines personnes pensent encore qu'un société à croissance infini dans un monde avec des ressources limités, ca leur semble logique. Bref.
Pourquoi mon site ou mon aplication, où le but principal est de transmettre des informations ne pourrait pas fonctionner sans CSS ou du Javascript ?
Voir même, ne vous sentez pas obligé d'acheter le dernier smartphone pour voir qu'actuellement je suis en alternance. Vous vous rappellez quand c'était trendy de parler de GreenIT ?
a11y
Bref, je veux que mon site puisse accueillir le maximum de personne, que ce soit avec un Iphone 3Gs ou des personnes ayant des troubles.
Ca sera une amélioration continu des normes RGAA et WAI. Ce n'est pas parfait, j'ai appris beaucoup de chose à ce niveau-là. Il faudrait que je me forme plus profondément sur ce sujet à l'avenir.
Performance
Au niveau des performances, même pour un site simple, la performance repose surtout sur des optimisations "basiques" qui ne coutent rien terme cognitive : images, compression et cache.
Les Images
Les images sont rapidement devenues le principal facteur de ralentissement avant
que tout soit en cache, ce qui n'est pas du optimal.
J'ai appliqué quelques bonnes pratiques pour la gestion de la performance des
images, comme peut le faire Next.js avec son composant <Image /> :
Conversion
.pngvers.webp, beaucoup plus léger à qualité équivalente et qui a une plus grande compatibilité que le format.avif.Définition explicite de la taille des images pour éviter les décalages de layout (CLS)
Chargement différé avec
loading="lazy"pour les images hors écranChargement prioritaire (
loading="eager"c'est le comportement par défaut) pour les images importantes.Utilisation de
decoding="async"pour ne pas bloquer le rendu de la page.
Exemple :
<img
class="card-image"
src="/images/<%= post.image %>"
alt="card image"
loading="lazy"
decoding="async"
/>
On peut voir que même sans Javascript, des images mal optimisées peuvent affecter lourdement les performances.
La Compression HTTP
Même si je n'envoie "que" du HTML et du CSS, la compression HTTP est indispensable.
J'utilise le middleware compression, qui active automatiquement gzip ou
brotli selon le navigateur.
Cela permet de réduire la taille des réponses:

Ce qui fait environ 75% de réduction, et donc moins de bande passante consommée et un temps de chargement plus rapide.
C'est probablement l'optimisation avec le meilleur ratio effort et gain.
Le Cache
Le cache navigateur est extrêmement puissant mais on peut vite se prendre les pieds dans le tapis.
J'ai configuré un cache assez long pour mes assets statiques (CSS et images) :
app.use(
express.static("public", {
maxAge: "30d", // cache de 30 jours
immutable: true // Pour éviter tout revalidation inutile
})
);
Cela permet au navigateur de ne jamais re-télécharger ces fichiers tant qu'ils ne changent pas.
Le problème que j'ai rencontré et qui assez classique c'est :
Comment recharger le navigateur pour récupérer la nouvelle version lorsqu'un fichier change ?
J'ai découvert le cache busting, qui est simplement la destruction de ce qui stocké en cache ou du moins seulement les fichiers qui ont changés.
J'ai résolu ce problème en modifiant le nom du fichier avec un hash unique :
import crypto from "crypto";
const hash = crypto
.createHash("sha1")
.update(minified)
.digest("hex")
.slice(0, 8);
Ce qui donne à la fin : bundle.${hash}.css
A chaque modification, le hash change
Le navigateur va enfin télécharger automatiquement la nouvelle version
Ca me permet d'avoir un cache agressif et d'éviter d'avoir du contenu obsolète en production.
La Minification CSS
Même avec du CSS "simple", le poids peut vite augmenter à mesure que le site évolue.
J'ai d'abord utilisé clean-css pour minifier mes fichiers, ce qui permet de :
Supprimer les espaces et commentaires
Réduire la taille du fichier final
Cependant, le projet est en maintenance. Donc, pour des projets plus complexes, je me tournerai vers des alternatives comme cssnano ou esbuild, qui sont plus pertinentes et activement maintenues.
Je reste tout de même avec clean-css car ca reste une minification simple, c'est suffisant.
Conclusion
Ça m'a fait du bien de revenir à des bases et comprendre pourquoi le web moderne est dans l'état actuel. Je vois maintenant comment certains outils fonctionnent ainsi que leur utilité - Merci les outils comme Vite ou Webpack d'exister.
Ce projet m'a aussi donné envie d'explorer des sujets comme l'accessibilité, les Web Components, la sécurité ou encore la génération d'un flux RSS que je n'ai pas trop détaillé ici, mais qui fera peut être le sujet d'un prochain article ,plus court. On verra si j'arrive à me tenir à un article tous les deux mois.