O post despublicado que não morria: por que a API responde 200 e não 404
4 de agosto de 2026 · Time Orion
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:
- A revalidação marca a entrada como obsoleta.
- A página é visitada; o framework refaz a busca.
- A API responde 404.
- O 404 não substitui a entrada obsoleta — não há resultado novo para gravar.
- 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:
{ "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.