
# Liens courts

Un lien collé dans une newsletter, dans un tweet et sur un flyer imprimé, c'est trois liens du point de vue de vos statistiques. Ou bien un seul lien, dont vous ne pouvez attribuer les clics à aucun des trois. Passer par un raccourcisseur hébergé règle l'attribution, et confie les données de clic, visiteur par visiteur, à la base de données de quelqu'un d'autre.

Le tableau `links` déclare des liens courts servis par votre application et comptés dans votre propre base de données :

```yaml
links:
  - slug: promo-printemps
    to: https://example.com/tarifs
    title: Promo printemps — page tarifs
    tags:
      - lancement-t2
    utm:
      source: newsletter
      medium: email
      campaign: printemps
    lifecycle:
      validUntil: '2026-06-30T23:59:59Z'
      maxClicks: 5000
```

Chaque lien répond sur `/l/{slug}`, par exemple `https://votre-app.com/l/promo-printemps`. Le préfixe `/l` est fixe et non configurable : l'adresse est donc prévisible sur toute instance Sovrium.

## Propriétés d'un lien

| Propriété   | Description                                                                                                                        |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `slug`      | L'adresse. Minuscules, chiffres, tirets et tirets bas. Doit être unique dans l'application.                                        |
| `to`        | Où arrive le visiteur : une URL absolue `http(s)://`, ou un chemin relatif à la racine de votre application.                       |
| `targets`   | Plusieurs destinations entre lesquelles répartir le trafic. Incompatible avec `to`. Voir [Répartir le trafic](#rpartir-le-trafic). |
| `title`     | Un libellé lisible destiné à la console d'administration. Jamais montré aux visiteurs.                                             |
| `tags`      | Étiquettes pour regrouper les liens dans la console : une campagne, un canal, un client.                                           |
| `notes`     | Texte libre à l'intention de qui maintient le lien. Ne quitte jamais la console.                                                   |
| `utm`       | Paramètres de campagne ajoutés à la destination. Voir [Paramètres de campagne](#paramtres-de-campagne).                            |
| `lifecycle` | Quand le lien est actif et combien de temps il dure. Voir [Cycle de vie](#cycle-de-vie).                                           |

Un lien court répond `302 Found` avec `Cache-Control: no-store`. C'est délibéré, et c'est la différence avec une [redirection](/fr/docs/redirects) : un `301` est mis en cache indéfiniment par les navigateurs et les intermédiaires, ce qui mettrait silencieusement en échec le comptage des clics comme la date d'expiration.

## Pourquoi pas une redirection ?

Les deux répondent à une URL avant la résolution des pages ; il vaut donc la peine de dire explicitement lequel choisir :

|          | `redirects`                                                           | `links`                                                               |
| -------- | --------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Le rôle  | Une page a déménagé et son ancienne URL doit continuer de fonctionner | Une adresse partageable dont vous voulez mesurer l'usage              |
| Identité | Un motif de chemin                                                    | Un objet nommé, doté d'une durée de vie                               |
| Statut   | `301`, qui indique aux moteurs que le déplacement est définitif       | `302`, jamais mis en cache                                            |
| Langues  | Hérite du préfixe de langue                                           | Délibérément une seule adresse, quelle que soit la langue du visiteur |
| Compté   | Non                                                                   | Oui, c'est tout l'intérêt                                             |

Aucun des deux ne remplace l'autre. Utilisez `redirects` pour une migration d'URL, et `links` pour tout ce que vous allez partager et dont vous voudrez lire les chiffres.

## Paramètres de campagne

`utm` ajoute les paramètres de campagne standard à la destination : vous les écrivez une fois dans la configuration au lieu de coller une chaîne de requête bricolée à la main dans chaque canal.

```yaml
links:
  - slug: webinaire
    to: https://example.com/inscription
    utm:
      source: linkedin
      medium: social
      campaign: webinaire-t2
      content: banniere-laterale
      term: automatisation
```

Un visiteur qui suit `/l/webinaire` arrive sur `https://example.com/inscription?utm_source=linkedin&utm_medium=social&utm_campaign=webinaire-t2&utm_content=banniere-laterale&utm_term=automatisation`.

Les paramètres déjà présents sur la destination sont conservés : `utm` n'écrase jamais une valeur que vous avez écrite vous-même. La chaîne de requête avec laquelle arrive le visiteur est elle aussi transmise.

## Cycle de vie

Un lien de campagne doit cesser de fonctionner quand la campagne se termine. `lifecycle` dit quand :

| Propriété    | Description                                                       |
| ------------ | ----------------------------------------------------------------- |
| `enabled`    | `false` retire le lien sans le supprimer. Vaut `true` par défaut. |
| `validFrom`  | Horodatage ISO 8601 avant lequel le lien n'est pas encore actif.  |
| `validUntil` | Horodatage ISO 8601 après lequel il cesse de répondre.            |
| `maxClicks`  | Cesse de répondre une fois ce nombre de clics atteint.            |

Un lien dont la période de validité n'a pas commencé répond `404`. Un lien expiré, désactivé, ou qui a épuisé son `maxClicks` répond `410 Gone` : un « ceci a existé et c'est terminé » explicite, plutôt que de faire comme si le lien n'avait jamais existé.

`maxClicks` porte deux limites, qu'il vaut mieux énoncer que laisser découvrir. Le total est lu dans vos données analytiques : seuls les clics **compris dans votre fenêtre de conservation** entrent dans le compte, et un lien plafonné qui survit à votre `analytics.retentionDays` repart donc avec un budget neuf. Le plafond, lui, est appliqué requête par requête plutôt que sous verrou : une rafale simultanée peut le dépasser légèrement. Les deux sont le prix à payer pour compter les clics à un seul endroit, au lieu de tenir un second décompte qui pourrait contredire le premier.

## Répartir le trafic

Donnez plusieurs `targets` à un lien pour répartir le trafic entre elles, par exemple pour un test A/B ou pour comparer deux pages de destination :

```yaml
links:
  - slug: test-tarifs
    title: Page tarifs A/B
    targets:
      - to: https://example.com/tarifs-a
        weight: 3
      - to: https://example.com/tarifs-b
        weight: 1
```

`weight` est relatif : cet exemple envoie donc trois visiteurs vers A pour un vers B. Une cible sans `weight` compte pour 1. Chaque clic enregistré porte la cible vers laquelle il a été résolu, si bien que la console d'administration présente chaque variante séparément.

## QR codes

Chaque lien dispose d'un QR code sur `/l/{slug}.svg`, généré par l'application sans service externe ni requête vers un tiers :

```text
https://votre-app.com/l/promo-printemps.svg
```

Le SVG est prêt à imprimer. Placez-le dans un flyer, une affiche, une carte de visite, ou dans un composant `image` de l'une de vos pages. Récupérer le SVG n'est pas un clic ; seul un scan en est un.

Un scan est compté séparément du même lien collé dans un message : « est-ce le flyer qui a marché, ou le tweet ? » devient une question qui a une réponse. La console d'administration montre les deux.

## Suivi

Les clics arrivent dans le même magasin d'événements que vos vues de page et respectent les mêmes réglages : `respectDoNotTrack`, `excludedPaths` et `retentionDays` s'appliquent tous, et aucun clic n'est enregistré tant que l'[analytique](/fr/docs/analytics) n'est pas activée. Les visiteurs sont comptés par le même hachage sans cookie utilisé partout ailleurs : aucun suivi de clic n'écrit de cookie.

Lisez les chiffres dans la console d'administration sur `/_admin/links` : l'évolution des clics dans le temps, les référents, les appareils, les campagnes et le journal brut des clics, pour tous les liens ensemble ou pour un lien à la fois.

## Créer des liens hors configuration

Les liens déclarés en configuration appartiennent à l'application : ils sont livrés et versionnés avec elle. Des liens peuvent aussi être créés dans la console d'administration, pour les campagnes qui vont et viennent plus vite qu'un déploiement.

La configuration l'emporte toujours. Un lien de console ne peut pas masquer un lien de configuration, et un lien déclaré en configuration est en lecture seule dans la console : vous pouvez l'y activer ou le désactiver, mais toute modification ou suppression est refusée. La configuration reste la source de vérité pour tout ce qu'elle déclare.

## Validation

Une configuration qui rendrait un lien inatteignable est rejetée au démarrage, plutôt qu'au moment où un visiteur tombe sur l'adresse morte :

- Aucun chemin de `pages`, `forms` ou `redirects` ne peut se trouver sous `/l` : il masquerait tous les liens courts situés en dessous
- `slug` doit être unique dans l'application
- Un lien doit déclarer `to` ou `targets`, et exactement l'un des deux

## Pages connexes

- [Redirections d'URL](/fr/docs/redirects) — retirer une URL sans casser les liens entrants
- [Analytique](/fr/docs/analytics) — le magasin de clics, ses réglages et sa conservation
- [Tableau de bord d'administration](/fr/docs/admin-dashboard) — où vivent les rapports de liens
