**TL;DR.** Redirecionamentos em ecommerce são sobre preservar a equidade de SEO através de mudanças de URL. Use 301 para mudanças permanentes (correções de slug, rebranding). Use 410 para exclusões permanentes. Acompanhe o histórico de slug por entidade para que URLs antigas redirecionem automaticamente. Detecte e achate cadeias semanalmente. Renderize páginas removidas com conteúdo útil, não genérico 404.

## Códigos de status HTTP para ecommerce

| Código | Nome              | Significado                       | Caso de uso                                          |
| ------ | ----------------- | --------------------------------- | --------------------------------------------------- |
| 200    | OK                | Página existe                     | Página normal de produto/categoria                   |
| 301    | Moved Permanently | Use a nova URL para sempre       | Mudanças de slug, rebranding, migrações de domínio   |
| 302    | Found             | Redirecionamento temporário       | Testes A/B, redirecionamentos geográficos, interrupções temporárias |
| 307    | Temporary Redirect | Igual a 302, mas preserva o método | Redirecionamentos de API com POST                    |
| 308    | Permanent Redirect | Igual a 301, mas preserva o método | Redirecionamentos de API com POST                    |
| 404    | Not Found         | Página não existe, pode voltar    | Erros de digitação, URLs mal lembradas               |
| 410    | Gone              | Removido permanentemente          | Produtos descontinuados, conteúdo deletado           |

Para ecommerce, o par comum é **301** para mudanças e **410** para exclusões permanentes.

## Quando usar 301

Cenários comuns de 301:

| Evento                          | Padrão                                              |
| ------------------------------- | --------------------------------------------------- |
| Correção de slug de produto (erro de digitação) | `/products/leather-bagg` → `/products/leather-bag`   |
| Rebranding de produto           | `/products/leather-bag` → `/products/heritage-bag`    |
| Reestruturação de categoria     | `/products/bags-leather` → `/categories/leather-bags` |
| Migração de domínio             | `oldbrand.com/...` → `newbrand.com/...`               |
| Mudança de URL de localidade    | `/products/bag` → `/en/products/bag`                  |
| Normalização de barra final     | `/products/bag/` → `/products/bag`                    |
| HTTP → HTTPS                    | `http://...` → `https://...`                          |

Implementação no Next.js:

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

Ou através de uma tabela de redirecionamento em tempo de execução:

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

## Quando usar 410

Um 410 Gone é a resposta certa quando:

- Um produto é descontinuado permanentemente.
- Um post de blog é removido por precisão ou razões legais.
- Uma categoria foi mesclada com outra e o slug antigo deve ser removido permanentemente.
- Uma página é intencionalmente deletada e não voltará.

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

A página 410 deve:

- Renderizar `<meta name="robots" content="noindex">`.
- Sugerir produtos similares (use pgvector cosine na incorporação da entidade removida).
- Fornecer uma caixa de pesquisa.
- Ter uma mensagem clara "este produto não está mais disponível".

## Histórico de slug por entidade

Quando um slug de produto muda, a URL antiga deve redirecionar para a nova URL. Armazenar isso manualmente é frágil. Acompanhe o histórico de slug por entidade:

```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()
);
```

Na atualização do 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}`]);
}
```

A tabela de histórico de slug permite que você rastreie a evolução da URL de um produto (útil para suporte e para arquivos legais/de conformidade).

## Cadeias de redirecionamento

Uma cadeia: `A → B → C`.

Por que cadeias são ruins:

- Cada salto perde 5–10% da equidade de link (o Google confirmou isso).
- Cada salto adiciona 100–500ms de latência.
- Três ou mais saltos aumentam a chance de o Google desistir de rastrear.

Detecção: HEAD-trace cada redirecionamento semanalmente.

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

  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 };
}
```

Achate cadeias: reescreva a origem para apontar diretamente para a URL final.

```ts
// Antes: A → B → C
await db.redirects.update({ from: '/a', to: '/b' });  // existente
// Após achatar:
await db.redirects.update({ from: '/a', to: '/c' });  // reescrito
```

## Laços de redirecionamento

`A → B → A` é fatal. Detecte no momento da escrita:

```ts
async function writeRedirect(from: string, to: string) {
  const wouldLoop = await detectLoop(from, to);
  if (wouldLoop) {
    throw new Error(`Redirect from ${from} to ${to} creates a loop`);
  }
  await db.redirects.insert({ from, to, statusCode: 301 });
}
```

## Ping IndexNow

Após escrever um redirecionamento:

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

O mecanismo busca novamente a URL antiga, vê o 301 e atualiza seu índice.

## Melhores práticas

- Mantenha a tabela de redirecionamento enxuta. Com o tempo, dezenas de pequenas edições de slug criam dezenas de redirecionamentos. Achate cadeias periodicamente.
- Não redirecione categorias inteiras para a página inicial. Isso é um 'soft 404' para o Google. Redirecione para a categoria equivalente mais próxima.
- Evite redirecionar para páginas noindex. Isso derrota o propósito de preservar a equidade.
- Documente a intenção do redirecionamento. Comentar a coluna na tabela de redirecionamentos ajuda você no futuro a entender o porquê.

## Como a Ordiko lida com redirecionamentos

- Tabela `storeRedirects` com colunas `from`, `to`, `statusCode`, `tenant`.
- Histórico de slug por entidade: `productSlugHistory`, `categorySlugHistory`, etc.
- Escrita automática de 301 na mudança de slug.
- `productLifecycle.archive()` escreve uma linha 410 na `storeRedirects`.
- A camada de caminhos removidos renderiza `/products/[slug]/gone` com recomendações de similaridade.
- Tarefa semanal do Trigger.dev `redirect-chain-verify.task.ts` HEAD-trace cada redirecionamento e sinaliza cadeias e alvos quebrados.
- Um clique "Achatar cadeia" reescreve A → C diretamente.
- Pings do IndexNow em cada criação de redirecionamento.

## FAQ

**Qual é a diferença prática entre 301 e 302?**
301 = permanente. Passa quase toda a equidade de link. Armazena o redirecionamento fortemente. Use para mudanças de slug, migrações de domínio. 302 = temporário. Passa menos equidade. Não armazena tão agressivamente. Use para testes A/B ou interrupções temporárias. Para a maioria dos redirecionamentos de ecommerce, 301 é o correto.

**Qual é a diferença entre 404 e 410?**
404 = 'não encontrado, pode voltar'. O Google pode manter a URL em seu índice por um tempo caso ela retorne. 410 = 'removido, permanentemente'. O Google descarta a URL mais rápido. Para produtos descontinuados permanentemente, 410 é uma melhor higiene de SEO.

**Quantos saltos de redirecionamento são demais?**
Dois saltos é o limite prático. Cada salto perde 5–10% da equidade de link e adiciona latência. Três ou mais saltos é um sinal. Detecte e achate-os automaticamente.

**Como a Ordiko lida com o histórico de slug?**
Cada tabela de entidade tem uma tabela de histórico de slug correspondente (por exemplo, product_slug_history). Na mudança de slug, uma nova linha é inserida com o slug antigo; um redirecionamento 301 é escrito automaticamente para storeRedirects. A tarefa semanal do Trigger.dev de verificação de cadeia de redirecionamento HEAD-trace redirecionamentos e sinaliza cadeias e alvos quebrados.