Créer un site web avec Astro.build : guide pratique
Publier sur le web avec des outils libres, performants et sobres : une réflexion sur les frameworks de publication statique, à mi-chemin entre veille technologique et mise en pratique.

Sommaire
- Présentation
- Structure d’un projet Astro
- Personnaliser l’apparence
- Modifications en profondeur
- Métadonnées des articles
- Mesurer l’audience sans cookie
- Déployer le site
- Conclusion
Présentation
Astro est un framework de développement web moderne, open-source (licence MIT, 100% libre) créé en 2021, soutenu par des sponsors et une communauté active.
Il permet de créer des sites rapides et optimisés. Contrairement à d’autres outils qui chargent beaucoup de JavaScript dans le navigateur, Astro génère des pages HTML statiques, ce qui rend les sites extrêmement performants.
Comme souvent pour ce type de frameworks, Astro propose une collection de thèmes prêts à l’emploi. J’ai porté mon choix sur AstroPaper qui offre une base solide, un mode sombre et une architecture responsive. L’installation se fait en clonant le dépôt GitHub du thème et en installant les dépendances avec npm.
# cloner le repo du thème
git clone https://github.com/satnaing/astro-paper.git
# se rendre dans le répertoire
cd AstroPaper
# installer le package
npm install
# executer le site en local
npm run dev
Le site est alors consultable dans le navigateur à l’adresse http://localhost:4321.
Structure d’un projet Astro
Un site Astro s’organise autour de quelques répertoires clés. Le dossier src contient tous les fichiers sources, avec notamment les pages qui correspondent aux URLs du site, les components qui sont des morceaux réutilisables d’interface, et les layouts qui définissent la structure générale des pages. Le répertoire public contient les fichiers statiques comme les images et le favicon. Les articles de blog sont stockés dans src/data/blog sous forme de fichiers Markdown, ce qui permet d’écrire du contenu sans toucher au code.
Voici l’organisation typique d’un projet Astro avec le thème AstroPaper :
mon-site/
├── public/
│ ├── favicon.png
│ ├── toggle-theme.js
│ └── goatcounter.js
├── src/
│ ├── assets/
│ │ ├── fonts/
│ │ ├── icons/
│ │ └── images/
│ ├── components/
│ │ ├── Header.astro
│ │ ├── Footer.astro
│ │ ├── Card.astro
│ │ ├── PostMeta.astro
│ │ └── TagList.astro
│ ├── data/
│ │ ├── blog/
│ │ │ ├── mon-article.md
│ │ │ └── images/
│ │ └── projects/
│ │ ├── mon-projet.md
│ │ └── images/
│ ├── layouts/
│ │ ├── Layout.astro
│ │ ├── PostDetails.astro
│ │ └── ProjectDetails.astro
│ ├── pages/
│ │ ├── index.astro
│ │ ├── a-propos.md
│ │ ├── mentions-legales.md
│ │ ├── posts/
│ │ └── projects/
│ ├── styles/
│ │ ├── global.css
│ │ └── typography.css
│ ├── utils/
│ │ ├── rehype-image-figure.ts
│ │ └── rehype-lead-heading.ts
│ ├── config.ts
│ ├── themes.ts
│ └── content.config.ts
├── package.json
└── astro.config.ts
Personnaliser l’apparence
La personnalisation d’un thème Astro commence par la configuration de base dans le fichier config.ts. On y définit le titre du site, l’auteur, la description, et divers paramètres comme le nombre d’articles par page ou la timezone. Pour modifier l’apparence visuelle, Astro utilise Tailwind CSS, un système qui permet d’appliquer des styles directement dans le code HTML via des classes utilitaires.
La typographie repose sur trois familles libres, chacune dédiée à un usage : Space Grotesk pour les titres, Inter pour le texte courant et la navigation, JetBrains Mono pour le code. Plutôt que de les charger depuis Google Fonts, elles sont hébergées avec le site : les deux premières via les paquets Fontsource, Inter dans sa version officielle, allégée aux seuls caractères latins. Les versions distribuées par Google suppriment en effet les variantes stylistiques d’Inter (« a » à une boucle, « l » à queue, guillemets arrondis…) que le site active en CSS. Bénéfice supplémentaire : aucune requête vers un service tiers, donc des pages plus rapides et aucune adresse IP transmise à Google.
Les couleurs du site reposent sur des variables CSS, que le fichier global.css relie aux classes Tailwind. Le thème propose un mode clair et un mode sombre qui suivent par défaut les préférences du système, et qu’un bouton du menu permet d’inverser.
Pour aller plus loin, j’ai centralisé les couleurs dans un fichier themes.ts qui expose des variables CSS nommées (fond, texte, titres, accent, bordure…). Le thème actif est sélectionné dans config.ts ; les variables sont injectées dans le <head> par le layout de base, et aucune couleur n’est écrite en dur ailleurs dans le code. Cela permet de basculer entre palettes — actuellement Catppuccin Latte en clair, retravaillé en bicolore (texte gris ardoise, titres et liens presque noirs, onglet actif et tags en ocre), et, en sombre, une palette inspirée du thème Dark+ de VS Code (gris neutres, bleu des liens, touches de couleur reprises de la coloration syntaxique) — sans toucher au reste du code. Chaque association de couleurs respecte les contrastes recommandés pour l’accessibilité (WCAG AA).
Pour le mode clair, mon choix de colorscheme ne change pas depuis quelques années : Catppuccin est le plus abouti que j’ai pu découvrir dans mes aventures de ricer linux.

Modifications en profondeur
J’ai modifié en profondeur le thème AstroPaper, base technique irréprochable et facilement modelable.
La homepage a été entièrement refondue autour d’un hero minimaliste sur deux colonnes : le titre en dégradé, une courte présentation et les liens vers les espaces de publication externes à gauche, la photo de profil à droite. Sur mobile, les deux blocs s’empilent. Le fichier pages/index.astro ne contient plus aucune des sections par défaut du thème.
La navigation a été repensée en une barre unique aux bords arrondis, qui flotte en haut de la page : le lien Accueil à gauche, les sections, la recherche et le bouton de thème à droite. Elle reste visible pendant le défilement et son fond devient translucide et flouté dès que le contenu passe dessous. Sur mobile, un menu hamburger ouvre un panneau vertical avec les sections, la recherche et la bascule de thème ; il se referme avec Échap, un clic à l’extérieur ou le choix d’un lien. Les sections ont été renommées pour refléter leur usage : Réflexions, Laboratoire, À propos.


J’ai ajouté des vignettes pour illustrer chaque article listé dans la page /posts.
La deuxième collection de contenu, Laboratoire, présente des projets distincts des articles. Chaque fiche dispose de son propre frontmatter (deployUrl, repoUrl, order) et s’affiche dans une grille de tuiles avec vignette, identique à la page Réflexions : les deux pages utilisent le même composant Card.astro.
Articles et projets partagent aussi un en-tête épuré : le titre, une ligne discrète « auteur · date · licence », les éventuels boutons d’accès, puis un filet avant le contenu. Les tags sont regroupés en fin de page.
Recherche et tags
La recherche plein texte s’appuie sur Pagefind, qui indexe les articles et les projets au moment du build. Aucun serveur ni service externe : l’index est un ensemble de fichiers statiques publiés avec le site. Elle est accessible depuis l’icône loupe du menu.
Articles et projets peuvent recevoir des tags, déclarés dans le frontmatter. Chaque tag dispose de sa page, qui regroupe les articles puis les projets concernés, et la page /tags les liste tous. Les tags reprennent l’apparence de l’onglet actif du menu.
Légendes d’images
La syntaxe Markdown standard ne prévoit pas de légendes pour les images. Pour y remédier sans passer par du HTML brut, j’ai écrit un plugin rehype sur mesure (src/utils/rehype-image-figure.ts), branché dans astro.config.ts. Il intercepte les nœuds <img> dont l’attribut title est renseigné et les enveloppe automatiquement dans un bloc <figure><figcaption>. La syntaxe reste du Markdown pur :

Le plugin détecte le titre, crée le <figure>, y déplace l’image et insère un <figcaption> avec le texte. Les styles de la légende (couleur, taille, alignement) sont définis dans typography.css.
Chapeau automatique
Chaque fichier Markdown commence par un titre #, qui faisait doublon avec le titre de la page affiché par le layout. Plutôt que de modifier les contenus, un second plugin rehype (src/utils/rehype-lead-heading.ts) transforme ce premier titre en chapeau : un paragraphe d’introduction en plus grand corps. Il ne s’applique qu’aux articles et aux projets. Chaque page ne conserve ainsi qu’un seul titre principal, ce qui est plus clair pour les lecteurs d’écran comme pour le référencement.
Liens-boutons dans le contenu
Pour insérer un lien sous forme de bouton dans une page Markdown — sans passer par du HTML verbeux — une classe CSS .btn est définie dans global.css. Elle applique le style des boutons (fond coloré, bordure, icône automatique via ::before) à n’importe quel <a> :
<a href="https://..." target="_blank" class="btn">Accéder au site</a>
L’icône est injectée en CSS pur via un pseudo-élément ::before et un mask-image en data URI SVG, ce qui évite tout SVG dans le contenu.
Métadonnées des articles
Chaque article Markdown commence par un bloc de métadonnées appelé frontmatter, délimité par trois tirets. Ces informations définissent les propriétés de l’article et sont utilisées par Astro pour générer les pages et les listes. Voici un exemple typique de frontmatter pour un article :
---
author: Raphaël
title: "Mon premier article"
pubDatetime: 2026-01-10T12:00:00Z
modDatetime: 2026-01-10T14:30:00Z
ogImage: "./images/illustration.png"
featured: false
draft: false
description: "Une description concise de l'article"
licence: "cc-by"
tags: ["open source", "web"]
---
Mesurer l’audience sans cookie
Le site a d’abord utilisé Google Analytics, avant de passer à GoatCounter. Google Analytics dépose des cookies qui, en France, imposent de recueillir le consentement du visiteur avant tout chargement, et donc d’afficher un bandeau. GoatCounter est un outil libre qui ne dépose aucun cookie et ne produit que des statistiques agrégées : il n’y a pas de bandeau à afficher, et son script pèse quelques kilo-octets.
L’intégration se fait en deux temps. On renseigne d’abord le code du compte GoatCounter dans config.ts. Le layout principal charge ensuite le script, uniquement en production grâce à la condition import.meta.env.PROD.
Une subtilité : la navigation d’Astro (ClientRouter) change de page sans recharger le navigateur. Le comptage automatique du script ne verrait donc que la première page de chaque visite. On le désactive (no_onload), puis un petit fichier public/goatcounter.js, chargé juste après, déclenche le comptage à chaque changement de page :
<script
id="goatcounter-js"
data-goatcounter="https://<code>.goatcounter.com/count"
data-goatcounter-settings='{"no_onload": true}'
async
src="https://gc.zgo.at/count.js"
></script>
<script src="/goatcounter.js"></script>
// public/goatcounter.js
document.addEventListener("astro:page-load", () => {
const count = () =>
window.goatcounter.count({ path: location.pathname + location.search });
if (window.goatcounter?.count) count();
else
document
.getElementById("goatcounter-js")
?.addEventListener("load", count, { once: true });
});
Les données collectées sont décrites dans la page de mentions légales, accessible depuis le pied de page.
Déployer le site
Configuration Pipeline
Pour déployer le site, créer la configuration de la pipeline GitLab CI/CD : le fichier .gitlab-ci.yml doit se trouver à la racine du projet. La mienne comporte deux étapes : un job lint contrôle le code (ESLint, Prettier) à chaque push, puis le job pages construit et publie le site, uniquement depuis la branche principale. La version de pnpm est celle fixée par le champ packageManager du package.json, identique en local et sur le serveur.
default:
image: node:24
before_script:
- npm install -g "$(node -p "require('./package.json').packageManager")"
- pnpm install --frozen-lockfile
stages:
- test
- deploy
lint:
stage: test
script:
- pnpm lint
- pnpm format:check
rules:
- if: $CI_COMMIT_BRANCH
pages:
stage: deploy
script:
- pnpm run build
- rm -rf public
- mv dist public
artifacts:
paths:
- public
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Les versions récentes de pnpm bloquent par défaut les scripts d’installation des dépendances. Ceux d’esbuild et de sharp, indispensables à Astro, doivent être autorisés dans un fichier pnpm-workspace.yaml versionné à la racine du projet, sans quoi la pipeline échoue :
allowBuilds:
esbuild: true
sharp: true
Configuration Domaine
Dans astro.config.ts, configurer le site pour le domaine personnalisé :
export default defineConfig({
site: "https://www.votredomaine.com",
// PAS de base path pour un domaine personnalisé
});
Conclusion
Après avoir, par le passé, réalisé plusieurs landing pages avec les techno Jekyll ou Hugo, je trouve le framework Astro rapide et performant. Une bonne documentation, de très nombreux thèmes, un stack technique de qualité, et tout cela sur un projet open-source !