# O post despublicado que não morria: por que a API responde 200 e não 404

> Resposta de erro não entra no cache de dados. Consequência: o conteúdo removido continuava sendo servido do cache velho para sempre. A correção foi trocar o código de status.

Publicado em 2026-08-04 por Time Orion · Blog da Orion
Versão canônica: https://orion.moe/blog/cache-de-dados-e-o-404-imortal
Índice do blog: https://orion.moe/blog

---

Encontramos este em produção, no dia em que o blog subiu — e ele é o tipo de bug que só aparece quando você testa o caminho de remoção, que é justamente o caminho que ninguém testa.

## O sintoma

Publicamos um post de teste. Ele apareceu no site em segundos, como esperado. Apagamos o post e disparamos a revalidação.

O índice atualizou na hora: o post sumiu da listagem. Perfeito.

Mas a **página do post continuou respondendo 200**, com o conteúdo inteiro. Na segunda visita, na terceira, depois de aguardar — continuava lá. Um post que não existia mais no banco continuava público na internet.

## A causa

O site busca o post numa API interna com cache por tag. Quando o post existe, a API responde 200 com o JSON, e esse resultado entra no cache de dados do framework. Quando o post não existe, a API respondia **404**.

E aqui está o detalhe que explica tudo: **resposta não-2xx não entra no cache de dados**.

É uma decisão sensata do framework — cachear erro é como se transforma uma indisponibilidade momentânea em falha prolongada. Só que a consequência, no nosso caso, era perversa:

1. A revalidação marca a entrada como obsoleta.
2. A página é visitada; o framework refaz a busca.
3. A API responde 404.
4. O 404 **não substitui** a entrada obsoleta — não há resultado novo para gravar.
5. O que o framework tem para servir continua sendo o conteúdo antigo.

O post virou imortal. A entrada nunca era substituída porque a resposta que deveria substituí-la não era cacheável.

## A correção

Trocamos o contrato da API. Post inexistente ou despublicado passou a responder:

```json
{ "post": null }
```

Com status **200** e o mesmo cabeçalho de cache do caminho de sucesso.

Agora o passo 4 funciona: o `null` é um resultado legítimo, entra no cache, substitui o conteúdo velho — e o site, ao receber `null`, renderiza a página de "não encontrado" de verdade.

Um post apagado passa a sumir na visita seguinte.

## "Mas 404 não é o código semanticamente correto?"

Para uma API pública consumida por humanos e por clientes HTTP genéricos, sim, seria.

Mas essa API tem exatamente um consumidor, e é um consumidor com cache. O contrato correto é o que funciona **para o consumidor real** — e aqui a ausência do recurso é um *estado válido do dado*, não uma falha da requisição. A requisição funcionou perfeitamente; a resposta é "não existe".

A distinção é essa: **404 diz "sua requisição falhou"; `{post: null}` diz "sua requisição funcionou e a resposta é vazia"**. Para um sistema de cache, a diferença é tudo.

Vale registrar o limite: se um dia essa API virar pública para terceiros, a decisão precisa ser revisitada — provavelmente com dois caminhos.

## A lição que generaliza

Toda camada de cache tem uma pergunta que quase ninguém faz: **o que acontece com a entrada quando a origem deixa de ter a resposta?**

Três respostas possíveis, e só uma costuma estar implementada:

- a origem responde o vazio, e o vazio é cacheável → **a entrada é substituída** (correto);
- a origem responde erro → a entrada obsoleta sobrevive (o nosso bug);
- a origem não responde → depende da política de stale, e provavelmente serve o velho (aceitável por um tempo, perigoso para sempre).

E a lição operacional: **teste o caminho da remoção**. Publicar é o caminho feliz e é o que todo mundo testa. Despublicar, apagar, revogar acesso — esses são os caminhos onde o cache guarda o que não deveria, e onde o bug tem consequência de segurança, não só de correção.

## O resumo

Quando o vazio é um estado legítimo do dado, ele precisa ser cacheável como qualquer outro. Responder erro para "não existe" faz o cache guardar a última versão boa — para sempre.

Custou uma linha e um comentário explicando por que 200 e não 404. O comentário é a parte importante: sem ele, a próxima pessoa "corrige" de volta.
