Skip to content

fix(routes): constrain year and month params to numbers - #3

Open
Guajir0-code wants to merge 1 commit into
sourcevortex:mainfrom
Guajir0-code:fix/archive-route-constraints
Open

fix(routes): constrain year and month params to numbers#3
Guajir0-code wants to merge 1 commit into
sourcevortex:mainfrom
Guajir0-code:fix/archive-route-constraints

Conversation

@Guajir0-code

Copy link
Copy Markdown

Problema

Qualquer caminho de dois segmentos que não case com uma rota anterior é servido como uma página de arquivo vazia com HTTP 200, em vez de 404. O site produz assim um número ilimitado de páginas fantasma indexáveis.

Evidência

Verificado em https://beta.sourcevortex.com.br/ no dia da abertura deste PR:

URL Status Componente renderizado
/foo/bar 200 YearMonthPosts (vazio)
/abcd/ef 200 YearMonthPosts (vazio)
/9999/99 200 YearMonthPosts (vazio)

Todos deveriam ser 404.

Causa

routes/web.php declara as duas rotas de arquivo sem nenhuma restrição de parâmetro:

Route::get('/{year}/{month}', [PostController::class, 'yearMonthPosts']);
Route::get('/{year}/{month}/{slug}', [PostController::class, 'show']);

Como estão no fim do arquivo, funcionam como catch-all: qualquer caminho de 2 ou 3 segmentos que não tenha casado antes chega aqui. O controller então executa

->whereYear('post_date', 'foo')->whereMonth('post_date', 'bar')

que simplesmente não casa nada. A página renderiza vazia e o Laravel responde 200, porque do ponto de vista dele a requisição foi atendida com sucesso.

Três consequências:

  1. Soft-404 infinito. Buscadores gastam orçamento de rastreamento em páginas sem conteúdo e podem indexá-las.
  2. Cache poluído. WpPostService::getPostsByYearMonth() monta a chave com os valores crus (posts_year_month_foo_bar_page_1) e guarda por um dia. Cada caminho inventado vira uma entrada permanente.
  3. Ruído. Varreduras automatizadas (/wp-admin/..., /assets/...) respondem 200 em vez de 404.

Solução

Route::get('/{year}/{month}', [PostController::class, 'yearMonthPosts'])
    ->where(['year' => '[0-9]{4}', 'month' => '0[1-9]|1[0-2]'])
    ->name('year-month.posts');

As restrições espelham o permalink do WordPress em que as rotas foram modeladas (%year%/%monthnum%): ano com 4 dígitos, mês de 01 a 12 com zero à esquerda — que é exatamente o que WpPostResource emite ($date->format('m')).

Preferi 0[1-9]|1[0-2] a um [0-9]{2} genérico porque /2026/99 continuaria sendo um soft-404 com o segundo.

Como validar

php vendor/bin/pest --filter=ArchiveRouteConstraintsTest
Tests: 17 passed (18 assertions)

Os testes cobrem caminhos não numéricos, meses fora de faixa, meses sem zero à esquerda, anos malformados, as mesmas variações na rota de post, e — importante — que caminhos válidos continuam chegando ao controller.

Verificado nos dois sentidos. Contra o código anterior, todas as asserções de 404 falham:

Expected response status code [404] but received 500.

(500 e não 200 porque no ambiente de teste não existem as tabelas wp_*; em produção o mesmo caminho responde 200, como na tabela de evidências acima.)

Impacto

  • Comportamento: URLs de arquivo válidas seguem idênticas. URLs malformadas passam a responder 404.
  • SEO: positivo. Elimina a fonte de soft-404 e para de consumir orçamento de rastreamento.
  • Schema: nenhuma alteração de banco.
  • Compatibilidade: nenhuma variável de ambiente nova.
  • Rollback: reverter o commit.

Nota sobre phpunit.xml

Contém a mesma alteração de duas linhas dos outros PRs desta série (fixar sqlite / :memory:), sem a qual a suíte não inicia. Se outro PR da série for mesclado primeiro, esta parte vira no-op — o conteúdo é idêntico.

Fora de escopo

  • Página 404 customizada. O tema WordPress anterior tinha 404.php; aqui o 404 é o padrão do Laravel. Vale um PR próprio.
  • A chave de cache ainda usa valores crus da URL em getPostsByYearMonth(). Este PR corta o vetor pela rota, mas a normalização de chaves de cache é tratada no PR de busca.

/{year}/{month} and /{year}/{month}/{slug} carried no constraints, so they
matched any 2- and 3-segment path. /foo/bar rendered an empty YearMonthPosts
page with HTTP 200 instead of 404, producing unlimited indexable soft-404s and
feeding a cache entry per distinct path.

Constrain year to 4 digits and month to 01-12, mirroring the WordPress
permalink structure the routes were modelled on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant