**TL;DR.** Les redirections dans le commerce électronique concernent la préservation de l'équité SEO lors des changements d'URL. Utilisez 301 pour les déplacements permanents (corrections de slug, rebranding). Utilisez 410 pour les suppressions permanentes. Suivez l'historique des slugs par entité afin que les anciennes URL redirigent automatiquement. Détectez et aplatissez les chaînes chaque semaine. Rendre les pages disparues avec un contenu utile, pas un 404 générique.

## Codes de statut HTTP pour le commerce électronique

| Code | Nom                | Signification                   | Cas d'utilisation                                   |
| ---- | ------------------ | ------------------------------- | --------------------------------------------------- |
| 200  | OK                 | La page existe                  | Page produit/catégorie normale                      |
| 301  | Déplacé définitivement | Utilisez la nouvelle URL pour toujours | Changements de slug, rebranding, migrations de domaine |
| 302  | Trouvé             | Redirection temporaire          | Tests A/B, redirections géographiques, pannes temporaires |
| 307  | Redirection temporaire | Identique à 302 mais préserve la méthode | Redirections API avec POST                          |
| 308  | Redirection permanente | Identique à 301 mais préserve la méthode | Redirections API avec POST                          |
| 404  | Non trouvé         | La page n'existe pas, pourrait revenir | Fautes de frappe, URL mal mémorisées                |
| 410  | Disparu            | Supprimé définitivement         | Produits discontinués, contenu supprimé             |

Pour le commerce électronique, la paire courante est **301** pour les déplacements et **410** pour les suppressions permanentes.

## Quand utiliser 301

Scénarios courants pour 301 :

| Événement                       | Modèle                                              |
| ------------------------------- | ---------------------------------------------------- |
| Correction de slug de produit   | `/products/leather-bagg` → `/products/leather-bag`   |
| Rebranding de produit           | `/products/leather-bag` → `/products/heritage-bag`    |
| Restructuration de catégorie    | `/products/bags-leather` → `/categories/leather-bags` |
| Migration de domaine            | `oldbrand.com/...` → `newbrand.com/...`               |
| Changement d'URL de locale     | `/products/bag` → `/en/products/bag`                  |
| Normalisation de la barre oblique | `/products/bag/` → `/products/bag`                    |
| HTTP → HTTPS                   | `http://...` → `https://...`                          |

Implémentation dans Next.js :

```ts
// next.config.ts
export default {
  async redirects() {
    return [
      {
        source: '/products/leather-bagg',
        destination: '/products/leather-bag',
        permanent: true,  // 301
      },
    ];
  },
};
```

Ou via une table de redirection à l'exécution :

```ts
// src/middleware.ts ou proxy.ts
const redirect = await getRedirect(request.url);
if (redirect) {
  return NextResponse.redirect(redirect.to, redirect.statusCode);
}
```

## Quand utiliser 410

Un 410 Disparu est la bonne réponse lorsque :

- Un produit est définitivement discontinué.
- Un article de blog est supprimé pour des raisons d'exactitude ou légales.
- Une catégorie a été fusionnée avec une autre et l'ancien slug doit disparaître définitivement.
- Une page est intentionnellement supprimée et ne reviendra pas.

```ts
// Pseudocode
const gone = await isGonePath(storeId, request.url.pathname);
if (gone) {
  return new Response(renderGonePage(gone.suggestions), {
    status: 410,
    headers: { 'Content-Type': 'text/html' },
  });
}
```

La page 410 doit :

- Rendre `<meta name="robots" content="noindex">`.
- Suggérer des produits similaires (utilisez pgvector cosine sur l'embedding de l'entité disparue).
- Fournir une boîte de recherche.
- Avoir un message clair "ce produit n'est plus disponible".

## Historique des slugs par entité

Lorsqu'un slug de produit change, l'ancienne URL doit rediriger vers la nouvelle URL. Stocker cela manuellement est fragile. Suivez l'historique des slugs par entité :

```sql
CREATE TABLE product_slug_history (
  id SERIAL PRIMARY KEY,
  product_id UUID NOT NULL,
  old_slug TEXT NOT NULL,
  new_slug TEXT NOT NULL,
  changed_at TIMESTAMPTZ DEFAULT NOW()
);
```

Lors de la mise à jour du slug :

```ts
async function updateProductSlug(productId: string, newSlug: string) {
  const product = await db.products.findOne({ id: productId });
  const oldSlug = product.slug;

  if (oldSlug === newSlug) return;

  await db.products.update({ id: productId, slug: newSlug });
  await db.productSlugHistory.insert({ productId, oldSlug, newSlug });
  await db.storeRedirects.insert({
    from: `/products/${oldSlug}`,
    to: `/products/${newSlug}`,
    statusCode: 301,
  });
  await enqueueIndexNow([`/products/${oldSlug}`, `/products/${newSlug}`]);
}
```

La table d'historique des slugs vous permet de retracer l'évolution de l'URL d'un produit (utile pour le support et pour les archives légales/conformité).

## Chaînes de redirection

Une chaîne : `A → B → C`.

Pourquoi les chaînes sont mauvaises :

- Chaque saut perd 5–10 % d'équité de lien (Google l'a confirmé).
- Chaque saut ajoute 100–500 ms de latence.
- Trois sauts ou plus augmentent la probabilité que Google abandonne le suivi.

Détection : tracez chaque redirection chaque semaine.

```ts
async function traceRedirect(url: string, hops: number = 0): Promise<{ finalUrl: string; chainLength: number }> {
  if (hops > 5) return { finalUrl: url, chainLength: hops }; // garde de boucle

  const res = await fetch(url, { method: 'HEAD', redirect: 'manual' });
  if (res.status >= 300 && res.status < 400) {
    const location = res.headers.get('location');
    if (!location) return { finalUrl: url, chainLength: hops };
    return traceRedirect(location, hops + 1);
  }
  return { finalUrl: url, chainLength: hops };
}
```

Aplatissez les chaînes : réécrivez la source pour pointer directement vers l'URL finale.

```ts
// Avant : A → B → C
await db.redirects.update({ from: '/a', to: '/b' });  // existant
// Après aplatissement :
await db.redirects.update({ from: '/a', to: '/c' });  // réécrit
```

## Boucles de redirection

`A → B → A` est fatal. Détectez au moment de l'écriture :

```ts
async function writeRedirect(from: string, to: string) {
  const wouldLoop = await detectLoop(from, to);
  if (wouldLoop) {
    throw new Error(`La redirection de ${from} à ${to} crée une boucle`);
  }
  await db.redirects.insert({ from, to, statusCode: 301 });
}
```

## Ping IndexNow

Après avoir écrit une redirection :

```ts
await enqueueIndexNow([oldUrl, newUrl]);
```

Le moteur récupère à nouveau l'ancienne URL, voit le 301 et met à jour son index.

## Meilleures pratiques

- Gardez la table de redirection concise. Au fil du temps, des dizaines de petites modifications de slug créent des dizaines de redirections. Aplatissez périodiquement les chaînes.
- Ne redirigez pas des catégories entières vers la page d'accueil. C'est un 'soft 404' pour Google. Redirigez plutôt vers la catégorie équivalente la plus proche.
- Évitez de rediriger vers des pages noindex. Cela contredit l'objectif de préserver l'équité.
- Documentez l'intention de redirection. Une colonne de commentaire dans la table de redirections aide le futur vous à comprendre pourquoi.

## Comment Ordiko gère les redirections

- Table `storeRedirects` avec les colonnes `from`, `to`, `statusCode`, `tenant`.
- Historique des slugs par entité : `productSlugHistory`, `categorySlugHistory`, etc.
- Écriture automatique de 301 lors du changement de slug.
- `productLifecycle.archive()` écrit une ligne 410 dans `storeRedirects`.
- La couche des chemins disparus rend `/products/[slug]/gone` avec des recommandations de similarité.
- La tâche hebdomadaire `redirect-chain-verify.task.ts` de Trigger.dev trace chaque redirection et signale les chaînes et les cibles cassées.
- Réécriture "Aplatir la chaîne" en un clic réécrit A → C directement.
- Pings IndexNow à chaque création de redirection.

## FAQ

**Quelle est la différence pratique entre 301 et 302 ?**
301 = permanent. Transfère presque toute l'équité de lien. Met en cache la redirection de manière intensive. Utilisez pour les changements de slug, les migrations de domaine. 302 = temporaire. Transfère moins d'équité. Ne met pas en cache aussi agressivement. Utilisez pour des tests A/B ou des pannes temporaires. Pour la plupart des redirections de commerce électronique, 301 est correct.

**Quelle est la différence entre 404 et 410 ?**
404 = 'non trouvé, pourrait revenir'. Google peut garder l'URL dans son index pendant un certain temps au cas où elle reviendrait. 410 = 'disparu, supprimé définitivement'. Google supprime l'URL plus rapidement. Pour les produits définitivement discontinués, 410 est une meilleure hygiène SEO.

**Combien de sauts de redirection sont trop nombreux ?**
Deux sauts est la limite pratique. Chaque saut perd 5–10 % d'équité de lien et ajoute de la latence. Trois sauts ou plus sont un signe. Détectez et aplatissez-les automatiquement.

**Comment Ordiko gère-t-il l'historique des slugs ?**
Chaque table d'entité a une table d'historique de slug correspondante (par exemple, product_slug_history). Lors du changement de slug, une nouvelle ligne est insérée avec l'ancien slug ; une redirection 301 est automatiquement écrite dans storeRedirects. La tâche hebdomadaire redirect-chain-verify de Trigger.dev trace les redirections et signale les chaînes et les cibles cassées.