feat(redirects): honor the query a redirect source was scoped to - #520
Draft
igoramf wants to merge 2 commits into
Draft
feat(redirects): honor the query a redirect source was scoped to#520igoramf wants to merge 2 commits into
igoramf wants to merge 2 commits into
Conversation
… loop
A rule whose source and target resolve to the same page answers every request
with a redirect back to the URL just asked for: ERR_TOO_MANY_REDIRECTS.
Bulk migration exports carry these rows, and `normalizePath` manufactures more:
it reduces an absolute `from` to `new URL(p).pathname`, which drops the query.
A row scoped to one legacy query
https://www.example.com/aliancas?map=category-1 -> /aliancas
therefore becomes `/aliancas -> /aliancas` and takes the page down. One
production storefront hit this twice from the same CSV — once on `/`, via a
`http://blog.example.com/,/` row, and once on a category page.
The Fresh loader this SDK replaced (deco-cx/apps
`website/loaders/redirectsFromCsv.ts`) had a `from === to` guard for exactly
this. Restore it, comparing after normalization so the collapsed rows above are
caught too.
Host is compared whenever both sides carry one, so a genuine cross-origin
redirect to the same path (`/x -> https://other.example.com/x`) still fires.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stacked on #519, which stops query-scoped rows from looping by discarding them. This restores what they are supposed to do. `normalizePath` reduces a source to its pathname, so a rule written against one legacy query — `https://site/relogios?map=category-1 -> /relogios/todos` — had no way to express "only for that query". #519 drops such a row when it collapses onto its own target; every other one silently widens into a rule for the whole page, redirecting visitors who never had the query. The Fresh loader this SDK replaced (deco-cx/apps `website/loaders/redirectsFromCsv.ts`) matched the entire href, so these rules worked. Sites migrating off it lose them today. - `Redirect.search` carries the query the source was scoped to. Those rules live in `RedirectMap.scoped`, keyed by pathname, unreachable from a bare path. - `matchRedirect(pathname, map, search?)` — new optional third argument. Existing callers keep today's behaviour, so this is additive. - A scoped rule wins over a bare-path rule on the same pathname: it is the more specific match. - Matching is a subset of params, not equality, so utm_*/gclid appended to the request do not defeat the rule. - `workerEntry` passes `url.search`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
igoramf
force-pushed
the
fix/redirect-csv-query-self-loop
branch
from
September 1, 2026 11:19
6f611cc to
e5b07c7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O problema que sobra depois do #519
O #519 mata o loop descartando a regra. Isso está certo para um fix de incidente, mas cobra um preço: a linha descartada era um redirect legítimo.
normalizePathreduz a origem ao pathname, então uma regra escrita contra uma query legada não tem como dizer "só para essa query":Hoje ela vira uma regra para
/relogiosinteiro — redirecionando todo visitante da categoria, inclusive quem nunca teve a query. Quando o destino coincide com o próprio path, isso vira o loop do #519; quando não coincide, o estrago é mais silencioso: a página existe, mas ninguém consegue chegar nela.O loader Fresh que este SDK substituiu casava a href inteira (
isHref: true), então essas regras funcionavam. Todo site que migra de Fresh para TanStack as perde.O que muda
Redirect.searchcarrega a query da origem. Essas regras vivem emRedirectMap.scoped, indexadas por pathname, inalcançáveis a partir de um path pelado.matchRedirect(pathname, map, search?)— terceiro argumento opcional. Quem não passa mantém o comportamento de hoje, então é aditivo: nenhum call site existente quebra.utm_source,gclid,fbclidanexados à request não derrubam a regra.workerEntry.tspassaurl.search.Comportamento
Com a regra
/relogios?map=category-1 → /relogios/todos:/relogios?map=category-1/relogios/todos/relogios?map=category-1&utm_source=news/relogios/todos/relogios/relogios?map=outra-coisaE a linha do incidente volta a fazer sentido em vez de ser descartada:
/aliancas/aliancas?map=category-1/aliancasTestes
packages/blocks/src/sdk/redirectsQueryScoped.test.ts, 9 casos: query exata, prefixo?, params extras tolerados, regra multi-param exigindo todos, precedência sobre path puro, várias regras no mesmo pathname, blocos de CMS além de CSV, e regras sem query intocadas.Suíte do pacote: 756 passed (57 arquivos). Typecheck do monorepo: os 6 erros que aparecem são pré-existentes em
blocks-cli/scripts/migrate— mesmo número nomain, confirmado comgit stash.Ponto de review
A mudança de API é
matchRedirectganhando um parâmetro eRedirectMapganhando um campo. Ambos aditivos, masRedirectMapé um tipo exportado — quem construir um à mão (não vialoadRedirects) precisa do camposcoped. Se preferir, dá para deixá-lo opcional e tratarundefined; achei mais honesto exigir, já queloadRedirectssempre preenche.🤖 Generated with Claude Code
Summary by cubic
Restores the query-scoped redirect rules that the #519 fix was discarding. A source like
/relogios?map=category-1previously lost its query, widening into a rule for the whole path — looping against its own target or silently hijacking the page — and now carries it and matches it at request time.Redirect.searchholds the source query; these rules live inRedirectMap.scoped, keyed by pathname and unreachable from a bare path.matchRedirect(pathname, map, search?)gains an optional third argument; existing callers keep today's behavior.workerEntrypassesurl.searchso the rules fire in the worker.utm_*orgclidappended to the request don't defeat a rule.Migration
RedirectMap.scopedis a new required field; maps built by hand, not vialoadRedirects, need to add it.Written for commit 3c86eeb. Summary will update on new commits.