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.
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 CDN | C'est tout le site : du HTML déjà construit. |
| Exécuter des fonctions serveur | Les quatre routes /api/*. Sans elles, le site s'affiche mais n'est pas éditable. |
| Construire à chaque poussée | Une publication est un commit ; le site doit se reconstruire seul. |
| Offrir un stockage clé-valeur | Le comptage du débit, partagé entre instances. Nommé RATE_LIMIT. |
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.
| Plateforme | Ce qui sert les routes | Comptage des tentatives |
|---|---|---|
| découverte par arborescence | functions/ + wrangler.toml | liaison RATE_LIMIT |
| Netlify | netlify/ + netlify.toml | stockage d'objets du site |
| conteneur, VPS, autre | npm run serve | mé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.
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.
# 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
-
Créer le projet, branché sur le dépôt
Commande de build npm run buildDossier de sortie distBranche de production main— la même queGIT_BRANCHVersion de Node 20 ou plus (variable NODE_VERSIONsi besoin) -
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=… -
Déclarer la liaison clé-valeur
RATE_LIMITCréer un espace clé-valeur, puis le lier au projet sous le nom exact
RATE_LIMIT— c'est ce nom que le code cherche. -
Déployer, puis vérifier
Voir la liste de contrôle plus bas. Trois commandes et deux essais à la main.
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
wwwvers celui avec, ou l'inverse — une seule adresse canonique. - Mettre à jour
sitedansastro.config.mjs: c'est lui qui construit les URL canoniques et leshreflang.
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.
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/*' };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 plateforme | Pourquoi |
|---|---|
Les fonctions reçoivent bien un Request web standard | Le code lit request.headers, request.text(), request.formData(). |
| Les variables sont accessibles à l'exécution | Certaines plateformes exposent process.env, d'autres un objet de contexte. |
| Un stockage clé-valeur est disponible | Sinon, implémenter RateLimitStore sur ce que la plateforme offre. |
| Le temps processeur autorisé couvre ~350 ms | C'est le coût d'une vérification de clé sur /api/auth. |
| L'adresse de l'appelant est lisible | Le comptage de débit s'appuie dessus. |
| La taille maximale de requête atteint 20 Mo | Le plafond des envois d'images. |
Servir localement, comme en production
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
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.
# 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_LIMITest 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
/adminproduit un commit, puis une reconstruction. - Le domaine est en HTTPS, avec une seule adresse canonique.
sitedansastro.config.mjspointe 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 :
[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.