# Routage : redirections, réécritures et en-têtes

L'edge de Bastide peut rediriger une requête, la réécrire vers un autre chemin ou une autre origine, et poser des en-têtes de réponse, avant même que votre application ne la reçoive. Cette page présente les deux endroits où ces règles se déclarent, le simulateur qui les vérifie avant publication, et le retour à une version précédente.

## Deux endroits, deux usages

| | Règles de routage du projet | Routes du déploiement |
| --- | --- | --- |
| Où elles vivent | En base, par projet | Dans le fichier `glowsoft.json` du dépôt |
| Comment elles changent | Par l'API ou la ligne de commande, sans redéploiement | À chaque déploiement, avec le code |
| Ce qu'elles font | Redirections, réécritures internes et vers une origine externe | Redirections, en-têtes de réponse, réécritures internes |
| Syntaxe | Motifs simples `*` et `:nom` | Celle de `vercel.json` |
| Volume | Jusqu'à 50 000 règles | 2 048 règles par bloc |
| Historique | 90 jours de versions, retour en un geste | Celui des déploiements : voir [Restaurer](restore.md) |

Choisissez les règles du projet pour les redirections en masse et celles qui changent sans livraison de code (une campagne, une migration d'adresses). Choisissez `glowsoft.json` pour ce qui suit le code : les en-têtes, les réécritures d'une application monopage, les redirections qu'une version introduit.

### L'ordre d'évaluation

Pour chaque requête, l'edge applique dans cet ordre : la limite de débit du projet, la protection des prévisualisations, les règles de routage du projet, puis les routes du déploiement, puis le cache, et enfin l'application. Les règles du projet l'emportent donc sur celles du dépôt, et une réécriture de projet est relue par les routes du déploiement. Une requête redirigée ne lit rien au cache et ne réveille pas une application en veille.

## Les règles de routage du projet

Les règles du projet se publient et se lisent par l'API et la ligne de commande. Il n'y a pas encore d'écran de console pour elles, et elles ne sont pas ouvertes aux agents : le catalogue MCP n'a pas d'outil pour elles. Un compte de service ne les mène pas non plus.

### Une règle

| Champ | Sens |
| --- | --- |
| `source` | Le motif de chemin, qui commence par `/`. `*` couvre toute suite de caractères, `/` compris ; `:nom` couvre un segment. Pas d'expression régulière. |
| `destination` | Un chemin du site, ou une URL `https://` vers un hôte de la liste d'autorisation (réécriture seulement). `:nom` et `*` y reprennent les captures, dans l'ordre. |
| `type` | `redirect` ou `rewrite`. |
| `status` | Redirection seulement : 301, 302, 307 ou 308. Par défaut 308. |
| `conditions` | Jusqu'à 8 conditions sur un en-tête de requête : `equals`, `prefix`, `exists` ou `absent`. |
| `timeoutMs` | Réécriture externe : délai de réponse, 5 000 ms par défaut, 10 000 ms au plus. |
| `maxBytes` | Réécriture externe : taille de réponse, 5 Mo par défaut, 10 Mo au plus. |

L'ordre de la liste est la précédence : la première règle dont le motif et les conditions concernent la requête répond. Écrivez la règle précise avant la règle générale qui la recouvre ; dans l'autre sens, la règle précise ne servirait jamais, et le simulateur la refuse.

### Les origines externes

Une réécriture vers `https://hôte/…` n'est admise que si l'hôte figure dans `allowedHosts`, la liste d'autorisation du projet. Elle ne reçoit que des noms de domaine publics, en minuscules, sans port ni chemin : ni adresse IP, ni `localhost`, ni suffixe interne comme `.local` ou `.internal`. L'edge vérifie de nouveau à l'exécution l'hôte, le délai et la taille.

### Publier

`routingrules.write` remplace la liste entière du projet : envoyez toujours la liste complète, dans l'ordre. Elle devient une version numérotée, prise en compte par chaque nœud à sa passe suivante, sans redéploiement. Une liste identique à la version active n'écrit rien.

```json
{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "rules": [
    { "source": "/ancien/:page", "destination": "/nouveau/:page", "type": "redirect", "status": 301 },
    { "source": "/api/*", "destination": "https://api.exemple.fr/v1/*", "type": "rewrite", "timeoutMs": 3000 }
  ],
  "allowedHosts": ["api.exemple.fr"],
  "urls": [{ "path": "/ancien/tarifs" }, { "path": "/api/produits" }],
  "ifVersion": 3,
  "idempotencyKey": "routage-2026-10-26"
}
```

`ifVersion` porte la version active que vous avez lue (0 si le projet n'en a aucune). Si quelqu'un a publié entre-temps, l'appel est refusé en 409 plutôt que d'écraser sa version.

### Le simulateur

Avant d'écrire quoi que ce soit, la publication rejoue la liste. Une liste refusée n'écrit rien : la réponse est un 422 qui détaille chaque conflit. Avec `dryRun: true`, rien n'est jamais écrit et le rapport est rendu, même s'il refuse.

Les conflits (`conflicts`) portent une nature :

| Nature | Sens |
| --- | --- |
| `loop` | Une chaîne de règles revient sur elle-même, ou dépasse 8 sauts. |
| `shadowed` | Une règle n'est jamais atteinte : une règle plus générale la précède. |
| `collision` | Deux motifs à jokers se recoupent sans que l'ordre les départage. |
| `duplicate` | Deux règles identiques. |
| `invalid` | Une forme refusée, ou une réécriture vers un hôte hors de la liste. |
| `incomplete` | La simulation a atteint son budget de comparaisons sans conclure. |

Pour chaque URL du jeu fourni dans `urls` (1 000 au plus, avec ses en-têtes éventuels), `results` donne l'issue : `redirect`, `rewrite`, `external`, `none` (aucune règle) ou `loop`, avec la destination, le statut et les règles traversées. Constituez ce jeu avec les adresses qui comptent pour vous : il sert de test de non-régression à chaque publication.

### Versions et retour en arrière

Chaque publication crée une version immuable : son numéro, sa date, son auteur, son nombre de règles et son empreinte. L'historique garde 90 jours de versions.

`routingrules.read` rend les règles de la version active, ou d'une version donnée, page par page (200 par défaut, 1 000 au plus), avec la liste d'autorisation et l'historique.

`routingrules.rollback` rétablit une version de l'historique. Il la recopie en une version neuve, après avoir rejoué le simulateur : l'historique reste linéaire, et le retour en arrière se défait à son tour en rétablissant la version d'avant.

```sh
bastide routingrules read --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567
bastide routingrules write --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --rules "$(cat regles.json)" --allowed-hosts '["api.exemple.fr"]' --dry-run
bastide routingrules rollback --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 --version 3
```

Par l'API : `GET` et `PUT /v1/projects/{projectId}/routing-rules`, et `POST /v1/projects/{projectId}/routing-rules/rollback`, sur `https://api.bastide.app`. Lire demande le rang Lecteur ; publier et revenir en arrière, le rang Mainteneur.

## Les routes du déploiement

Le fichier `glowsoft.json`, à la racine du dépôt, accepte trois blocs dont la forme et le sens sont ceux de `vercel.json` : `redirects`, `headers` et `rewrites`. Ils sont compilés à la construction et gelés avec le déploiement : les changer demande un nouveau déploiement, et restaurer une version précédente rétablit aussi ses routes. Une règle que l'edge ne saurait pas appliquer fait échouer la construction, avec un message qui situe la règle.

```json
{
  "redirects": [
    { "source": "/blog/:slug", "destination": "/articles/:slug", "permanent": true }
  ],
  "headers": [
    {
      "source": "/:path*",
      "headers": [{ "key": "Strict-Transport-Security", "value": "max-age=31536000" }]
    }
  ],
  "rewrites": [
    { "source": "/:path*", "destination": "/index.html" }
  ]
}
```

- **Redirections** : la première qui concerne la requête répond. Sans `permanent` ni `statusCode`, le statut est 308 ; `"permanent": false` donne 307. Les conditions `has` et `missing` portent sur un en-tête, un témoin, un paramètre de requête ou l'hôte. La clé `env` est refusée : l'edge ne détient pas les variables du projet.
- **En-têtes** : toutes les règles qui concernent la requête s'appliquent, dans l'ordre. Un en-tête que l'application pose elle-même l'emporte.
- **Réécritures** : le chemin demandé est servi d'abord ; une réécriture n'est essayée que si l'application y répond 404, trois au plus par requête. Une destination vers une autre origine est refusée : utilisez pour cela une règle de projet.

Comme dans `vercel.json`, un motif est sensible à la casse, et `/:path*` ne couvre pas la racine `/`.

### Les en-têtes de sécurité par défaut

L'edge pose quatre en-têtes de sécurité, gelés avec chaque déploiement :

| En-tête | Valeur |
| --- | --- |
| `X-Content-Type-Options` | `nosniff` |
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
| `X-Frame-Options` | `SAMEORIGIN` |
| `Permissions-Policy` | `browsing-topics=()` |

Un bloc `headers` les surcharge, et une valeur vide en retire un. `Strict-Transport-Security` et une politique de sécurité de contenu ne sont pas posés d'office : déclarez-les si vous les voulez.

## Limites

| Limite | Règles du projet | Routes du déploiement |
| --- | --- | --- |
| Nombre de règles | 50 000 | 2 048 par bloc |
| Poids | 4 Mo de JSON par projet ; corps de requête de 16 Mo | 256 Ko de règles compilées |
| Jokers par motif | 4 | — |
| Longueur d'un motif ou d'une destination | 2 048 caractères | 4 096 caractères |
| Conditions par règle | 8 | 16 `has` et 16 `missing` |
| Hôtes externes autorisés | 50 | aucun |
| Historique | 90 jours | celui des déploiements |

Ces limites sont les mêmes à tous les paliers.

## Erreurs fréquentes

| Symptôme ou code | Cause | Remède |
| --- | --- | --- |
| 422 à la publication | Le simulateur a trouvé un conflit. | Lire `conflicts`, corriger l'ordre ou la règle, rejouer avec `dryRun`. |
| 409 `conflict` | `ifVersion` ne correspond plus à la version active. | Relire avec `routingrules.read`, refaire vos modifications sur la version courante. |
| `invalid` sur une réécriture externe | L'hôte n'est pas dans `allowedHosts`, ou n'est pas un domaine public. | Ajouter l'hôte exact, en minuscules, sans port. |
| `shadowed` | Une règle générale précède une règle précise. | Placer la règle précise avant. |
| 508 servi au visiteur | Une chaîne de règles dépasse 8 sauts à l'exécution. | Simplifier la chaîne ; le simulateur la signale en `loop`. |
| La construction échoue sur `glowsoft.json` | Une route utilise une forme que l'edge n'applique pas. | Lire le message, qui nomme le bloc et le rang de la règle. |
| Une réécriture du dépôt ne s'applique pas | L'application répond autre chose que 404 sur le chemin demandé. | C'est le comportement voulu : le contenu existant l'emporte. |

## Voir aussi

- [Domaines](domains.md)
- [Cache et images](cache.md)
- [Restaurer](restore.md)
- [Migrer depuis Vercel](from-vercel.md)
- [routingrules.write](../reference/operations/routingrules.write.md), [routingrules.read](../reference/operations/routingrules.read.md), [routingrules.rollback](../reference/operations/routingrules.rollback.md)
