Mis à jour le 16 min de lecture

Création de ce site vitrine : Astro (SSG), Zapier et Mailjet — sans surcouche serveur inutile

Retour d’expérience sur la stack du site emmanuelsauvage.fr : génération statique, blog en Markdown (content collections + Zod), formulaire contact via proxy, Zapier et Mailjet (MJML).

  • Astro
  • Zapier
  • Mailjet
  • Architecture web
Schéma : site statique Astro, proxy contact, Zapier et Mailjet Astro (SSG) → proxy → Zapier → Mailjet Astro HTML statique PHP / proxy Zapier Mailjet Formulaire JSON same-origin · template transactionnel · pas de backend Node en prod

Objectifs du projet

Présenter une offre freelance (Symfony, API, data, outils métier), publier des articles techniques indexables, et offrir un contact fiable sans maintenir un serveur d’applications PHP ou Node pour un simple formulaire. Contraintes : temps de chargement correct, mise en page maîtrisée, conformité RGPD (politique, consentement, pas de trackers non essentiels par défaut).

Pourquoi Astro plutôt qu’un « gros » framework SPA ?

Les applications monopage (React/Vue seuls) envoient souvent un bundle volumineux avant que le contenu ne soit lisible. Pour une vitrine et un blog majoritairement lecture, l’intérêt est faible : on veut du HTML servi tout de suite, du CSS cohérent, et du JS uniquement là où c’est utile (formulaire, bandeau cookies, petites interactions).

Astro part de cette idée : par défaut, zéro JavaScript côté navigateur pour les composants ; les composants peuvent rester en HTML pur. Si besoin d’interactivité, on opte pour des îlots (islands) avec une framework UI au choix, ou du script inline ciblé — comme sur la page contact.

  • SSG : astro build produit des fichiers statiques ; pas de runtime Node obligatoire en production.
  • Routing par fichiers : src/pages/ → URLs prévisibles (/blog/, /contact/, etc.).
  • Composants réutilisables (.astro) : en-tête, pied, cartes, mise en page article — même syntaxe pour tout le site.
  • SEO : balises <title>, meta description, canonical et données structurées (JSON-LD) injectées par layout.

Ce que contient concrètement le dépôt

  • src/pages/ : les routes (accueil, services, à propos, contact, pages légales, blog/index.astro pour la liste, blog/[slug].astro pour générer une page par article).
  • src/layouts/ : gabarit global et gabarit article (titre, temps de lecture, JSON-LD blog).
  • src/components/ : navigation, pied de page, illustrations blog, bandeau cookies, etc.
  • src/content/blog/*.md + src/content.config.ts : collection blog (loader glob, schéma Zod sur le frontmatter : titre, description, dates, temps de lecture, tags, clé illustration pour le schéma SVG).
  • src/content/BLOG-AUTHORS.md : rappel pour ajouter un nouvel article (fichier .md, champs obligatoires, build qui valide le schéma).
  • public/ : assets servis tels quels (favicon, images, contact-zapier.php — voir plus bas).

Blog : content collections et validation au build

Les articles ne sont plus des fichiers .astro dupliqués : chaque billet est un Markdown dans src/content/blog/ (nom du fichier = slug d’URL). Au build, Astro charge la collection via getCollection('blog') ; la page [slug].astro appelle render() pour produire le HTML du corps. Si le frontmatter oublie un champ ou utilise une valeur d’illustration inconnue, Zod fait échouer le build — utile quand le nombre d’articles augmente.

  • Liste : blog/index.astro trie les entrées par pubDate décroissante.
  • SEO : titre et description viennent du frontmatter ; le layout article conserve canonical, Open Graph et JSON-LD.
  • Corps : Markdown et HTML autorisés (tableaux, <aside class="tldr">, FAQ en <details>, blocs de code, etc.).

Les dépendances npm restent limitées (Astro, polices, sharp pour le pipeline d’assets). Pas de base de données : le contenu du blog est versionné dans le dépôt comme le reste du site, avec une revue possible via PR sur les fichiers .md.

Formulaire de contact : le problème CORS

Zapier expose une URL Catch Hook en HTTPS. En pratique, le navigateur ne peut en général pas poster directement du JSON depuis votre domaine vers hooks.zapier.com : le navigateur applique la politique CORS ; le domaine Zapier ne renvoie pas les en-têtes qui autoriseraient un POST depuis n’importe quel site (et ce serait d’ailleurs risqué pour eux).

La solution retenue : le formulaire appelle une URL du même site (same-origin), qui rejoue la requête côté serveur ou outil de dev.

  • En développement (npm run dev) : un proxy Vite dans astro.config.mjs mappe /api/zapier-contact vers hooks.zapier.com avec rewrite vers le chemin du Catch Hook. Le navigateur ne voit que localhost.
  • En production (WAMP / Apache + PHP) : un fichier public/contact-zapier.php reçoit le POST JSON, valide les champs minimaux, et relaie avec curl vers l’URL Zapier. Aucun Node requis sur le serveur : le site déployé est statique + ce seul endpoint PHP.

Côté page contact.astro, l’URL de soumission est choisie selon l’environnement : import.meta.env.DEV → proxy ; sinon → /contact-zapier.php. Le corps envoyé est du JSON avec notamment name, company, email, message, submitted_at (ISO 8601).

Zapier : orchestrer sans coder un « back-office »

Zapier sert de couche d’intégration : lorsqu’un message arrive sur le Catch Hook, un Zap enchaîne les étapes — par exemple validation des champs, formatage, puis envoi via l’API Mailjet.

  • Avantages : pas de serveur à patcher pour un SMTP custom ; possibilité d’ajouter plus tard une notion Slack, une ligne Google Sheets, ou un filtre anti-spam sans redéployer le site.
  • Point d’attention : le flux dépend d’un service tiers ; surveiller les quotas et la bonne exécution des Zaps (logs Zapier). Pour une volumétrie très forte, on repenserait une file ou un endpoint dédié.

Mailjet : emails transactionnels et templates

Mailjet prend le relais pour la délivrabilité (SPF/DKIM/DMARC sur le domaine d’envoi), le suivi des bounces et un éditeur de templates transactionnels. Les champs saisis sur le site sont mappés dans Zapier vers les variables du template Mailjet.

Dans ce dépôt, le dossier email-templates/ contient une source MJML et le HTML généré, avec une documentation des variables Mailjet (syntaxe du type {{var:name}}, {{var:email}}, etc. — voir README-mailjet-zapier.md dans ce dossier) pour rester aligné avec ce que Zapier envoie. Workflow typique : modifier le MJML, recompiler en HTML (npx mjml …), mettre à jour le template côté Mailjet si besoin.

Sécurité et vie privée (rappel)

  • Le proxy PHP vérifie la méthode POST, parse le JSON et rejette les champs obligatoires manquants — pas d’exécution de code arbitraire à partir du corps brut.
  • En production, HTTPS est indispensable pour protéger les données en transit ; le fichier PHP ne remplace pas une politique de mots de passe pour l’admin Mailjet/Zapier.
  • Le site inclut une politique de confidentialité et un consentement explicite sur le formulaire ; les données ne servent qu’à répondre à la demande de contact, en cohérence avec l’usage décrit.

Ce qu’on pourrait ajouter plus tard

Les content collections Astro (Markdown + schéma Zod) sont déjà en place pour ce blog — ce qui suit concerne d’autres évolutions possibles, pas une migration à prévoir.

  • MDX ou composants dans le corps des articles si le Markdown seul ne suffit plus ; images optimisées via le pipeline Astro si la galerie photo grossit.
  • Protection anti-bot (hCaptcha / Turnstile) si le hook recevait trop de spam — souvent branché avant ou dans le Zap.
  • Hébergement sur CDN + CI qui lance astro build à chaque push, pour ne jamais oublier de régénérer le statique.

FAQ

Pourquoi ne pas utiliser un simple mailto: ?

Ouverture du client mail, pas de trace côté serveur, expérience inégale sur mobile, et pas de template Mailjet ni de chaîne Zapier. Le formulaire structuré donne des messages plus exploitables.

Astro impose-t-il TypeScript ou React ?

Non : les fichiers .astro suffisent pour ce site ; TS est optionnel ; React/Vue/Svelte ne sont ajoutés que si vous choisissez des îlots interactifs.

Le proxy PHP est-il obligatoire si je suis sur Netlify / Vercel ?

Non : on peut remplacer le script par une serverless function ou un endpoint fournisseur qui rejoue le POST vers Zapier. L’idée reste la même : éviter le POST cross-origin direct depuis le navigateur.