FR EN

Chapitre 16Mettre en ligne

Déploiement

Ce que l'hébergeur doit savoir faire, la configuration pas à pas, les variables en secrets, la liaison de comptage, et les vérifications qui décident si on livre ou non.

7 min de lecture10 sectionsChapitre 16 / 22

Ce que l'hébergeur doit savoir faire

Quatre capacités, et rien d'autre. Tout hébergeur qui les réunit convient.

CapacitéPourquoi
Servir des fichiers statiques sur un CDNC'est tout le site : du HTML déjà construit.
Exécuter des fonctions serveurLes quatre routes /api/*. Sans elles, le site s'affiche mais n'est pas éditable.
Construire à chaque pousséeUne publication est un commit ; le site doit se reconstruire seul.
Offrir un stockage clé-valeurLe comptage du débit, partagé entre instances. Nommé RATE_LIMIT.
Ce qui ne convient pas

Un hébergement purement statique — GitHub Pages, GitLab Pages, un seau de stockage, un serveur de fichiers. Le site s'afficherait parfaitement, /api/* répondrait 404, et l'édition serait impossible.

La configuration livrée

Un site créé porte un adaptateur par famille d'hébergeurs. Aucun ne contient de règle : les routes sont déclarées une seule fois, dans src/lib/api.ts. Les dossiers de l'hébergeur qu'on n'utilise pas se suppriment sans rien casser.

PlateformeCe qui sert les routesComptage des tentatives
découverte par arborescencefunctions/ + wrangler.tomlliaison RATE_LIMIT
Netlifynetlify/ + netlify.tomlstockage d'objets du site
conteneur, VPS, autrenpm run servemémoire — une seule instance

La configuration de la première tient en trois lignes. Rien d'autre dans le code ne dépend de l'hébergeur.

Un site créé avant create-inline 1.2.0

Il n'a qu'un adaptateur, et reste parfaitement fonctionnel chez son hébergeur d'origine. Pour gagner les deux autres : mettre à jour inline-core en 2.1.0 ou plus, créer src/lib/api.ts avec la ligne createRouter, y brancher les quatre fichiers de functions/api/, puis copier netlify/, netlify.toml, scripts/serve.mjs et scripts/build-netlify.mjs depuis un site créé avec cette version. Le plus simple reste d'en générer un à côté et d'y prendre les fichiers.

wrangler.toml toml
# Aucun secret ici : les variables se déclarent dans .dev.vars en local et dans
# la configuration du projet en production.
name = "boulangerie-martin"
compatibility_date = "2026-08-17"
pages_build_output_dir = "dist"

Déployer, pas à pas

  1. Créer le projet, branché sur le dépôt

    Commande de buildnpm run build
    Dossier de sortiedist
    Branche de productionmain — la même que GIT_BRANCH
    Version de Node20 ou plus (variable NODE_VERSION si besoin)
  2. Poser les variables en secrets d'exécution

    Pas en variables de build. Une variable de build peut se retrouver dans les fichiers servis ; un secret d'exécution n'est lisible que par la fonction.

    env env
    EDITOR_KEY_HASH=$argon2id$v=19$m=19456,t=2,p=1$…
    SESSION_SECRET=…
    EDITOR_NAME=Éditeur du site
    EDITOR_EMAIL=contact@boulangerie-martin.fr
    GIT_PROVIDER=github
    GIT_REPO=agence/boulangerie-martin
    GIT_BRANCH=main
    GIT_TOKEN=…
  3. Déclarer la liaison clé-valeur RATE_LIMIT

    Créer un espace clé-valeur, puis le lier au projet sous le nom exact RATE_LIMIT — c'est ce nom que le code cherche.

  4. Déployer, puis vérifier

    Voir la liste de contrôle plus bas. Trois commandes et deux essais à la main.

Les déploiements de prévisualisation

Si les mêmes secrets sont posés sur l'environnement de prévisualisation, une branche d'essai peut écrire dans la branche de production : la fonction écrit là où GIT_BRANCH pointe, pas là où elle est déployée. Deux réponses : ne pas poser les secrets sur les prévisualisations, ou y poser un GIT_BRANCH distinct.

Sans la liaison de comptage

Le comptage retombe sur un compteur en mémoire. Il fonctionne, il ne lève aucune erreur, et il ne protège rien en production : chaque instance compte pour elle seule, et un démarrage à froid remet le compteur à zéro. C'est exactement le genre de manquement qui ne se voit qu'à l'incident.

Le contrôle est simple : cinq clés fausses de suite sur /admin doivent finir par un message d'attente. Si la sixième tentative est encore refusée par « clé incorrecte », la limitation n'est pas active. Ne pas livrer.

Le domaine

  • Ajouter le domaine du client au projet, et attendre le certificat.
  • HTTPS est indispensable, pas seulement souhaitable : le cookie de session porte l'attribut Secure, donc il n'est pas envoyé en clair. Sur du HTTP, l'authentification ne peut pas fonctionner.
  • Vérifier la redirection du domaine sans www vers celui avec, ou l'inverse — une seule adresse canonique.
  • Mettre à jour site dans astro.config.mjs : c'est lui qui construit les URL canoniques et les hreflang.

Un autre hébergeur

Netlify et un simple processus Node sont déjà livrés — voir le tableau plus haut. Pour une plateforme qui n'a pas son adaptateur, il n'y a qu'un fichier à écrire, et il ne décide rien : api.handle choisit la route et la méthode, la logique reste dans le paquet.

un hébergeur sans adaptateur livré ts
import { api } from '../../src/lib/api';

// La plateforme fournit (request, context) ; le routeur attend (request, env).
export default async (request: Request) => api.handle(request, process.env);

export const config = { path: '/api/*' };
Sur Netlify : ne pas déclarer node_bundler

Avec node_bundler = "esbuild" dans netlify.toml, Netlify produit du CommonJS : l'export par défaut devient exports.default, la fonction est prise pour une v1, et l'exécution appelle handler — qui n'existe pas. Le site répond alors 502 « handler is not a function » au moment précis où le client entre sa clé, et rien n'indique que la clé n'y est pour rien.

Sans cette option, Netlify devrait résoudre lui-même le TypeScript d'inline-core, publié en source, ce que Node ne sait pas charger. D'où la commande de build en deux temps : npm run build && npm run build:netlify, qui assemble la fonction en un module ESM autonome. Elle vérifie son propre produit et fait échouer le build plutôt que le site déployé.

À vérifier sur une autre plateformePourquoi
Les fonctions reçoivent bien un Request web standardLe code lit request.headers, request.text(), request.formData().
Les variables sont accessibles à l'exécutionCertaines plateformes exposent process.env, d'autres un objet de contexte.
Un stockage clé-valeur est disponibleSinon, implémenter RateLimitStore sur ce que la plateforme offre.
Le temps processeur autorisé couvre ~350 msC'est le coût d'une vérification de clé sur /api/auth.
L'adresse de l'appelant est lisibleLe comptage de débit s'appuie dessus.
La taille maximale de requête atteint 20 MoLe plafond des envois d'images.

Servir localement, comme en production

bash bash
npm run build
npm run serve:functions     # site + fonctions sur http://127.0.0.1:8788

C'est le seul mode qui exécute réellement /functions. npm run dev sert le site sans les routes : utile pour la mise en page, inutile pour l'édition.

Vérifications avant de livrer

Celle-ci passe avant toutes les autres

Un site déposé sans ses fonctions se construit, se sert et s'affiche parfaitement. Seule l'édition échoue, au moment d'entrer la clé : /api/auth répond 404 au lieu d'ouvrir une session, et rien à l'écran ne dit pourquoi. Les soupçons se portent alors sur la clé, qui n'y est pour rien. Une commande tranche en une seconde.

bash bash
# 1. Les fonctions tournent — à vérifier avant tout le reste
curl -s -o /dev/null -w "%{http_code}
" https://le-site.fr/api/auth          # → 405

# 2. Le contenu est dans le HTML brut
curl -s https://le-site.fr/fr/ | grep -c "un titre de la page"        # → 1

# 3. L'écriture est fermée sans session
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://le-site.fr/api/save   # → 401

# 4. Les méthodes inattendues sont refusées
curl -s -o /dev/null -w "%{http_code}\n" https://le-site.fr/api/save           # → 405

# 5. Aucun secret dans les fichiers servis
curl -s https://le-site.fr/fr/ | grep -Ec "argon2|github_pat|GIT_TOKEN"         # → 0
  • Les cinq commandes ci-dessus donnent le résultat attendu.
  • Cinq clés fausses de suite aboutissent à un message d'attente.
  • La liaison RATE_LIMIT est déclarée et liée au bon projet.
  • Les variables sont en secrets d'exécution, pas en variables de build.
  • Le jeton est restreint au seul dépôt du site.
  • Une publication d'essai depuis /admin produit un commit, puis une reconstruction.
  • Le domaine est en HTTPS, avec une seule adresse canonique.
  • site dans astro.config.mjs pointe vers le domaine réel.
  • Les prévisualisations n'ont pas les secrets de production.

Revenir en arrière

Contenu

Annuler une publication

Chaque publication est un commit : git revert le commit fautif, poussez, le site se reconstruit. Aucune fonctionnalité à développer, aucune sauvegarde à restaurer.

Déploiement

Revenir à une version en ligne

Les hébergeurs conservent les déploiements précédents et savent en remettre un en service. C'est plus rapide qu'un correctif, et cela laisse le temps de comprendre.

Surveiller

Les journaux de la fonction sont le seul endroit où lire une panne d'écriture. On y trouve :

text text
[save] écriture impossible (unauthorized)     → jeton expiré ou révoqué
[save] écriture impossible (not_found)        → dépôt, branche ou chemin faux
[save] contenu refusé par le schéma [ … ]     → un contenu invalide a été soumis
[auth] débit dépassé                          → cinq tentatives, la limitation a joué
[upload] format refusé : svg                  → un fichier non image a été envoyé

Aucun de ces messages ne contient de secret : ni jeton, ni empreinte, ni cookie, ni contenu du client. C'est vérifié par check-logs.mjs à chaque commit.