fix(vnda): optimize /api/v2/tags/:name calls to prevent rate limiting#1563
fix(vnda): optimize /api/v2/tags/:name calls to prevent rate limiting#1563pandeiro7 wants to merge 1 commit intodeco-cx:mainfrom
Conversation
Tagging OptionsShould a new tag be published when this PR is merged?
|
📝 WalkthroughWalkthroughReplaced Changes
Sequence DiagramsequenceDiagram
participant PageLoader as Page Loader
participant URLParser as URL Parser
participant TagCache as Tag Cache/API
participant CategoryBuilder as Category Builder
PageLoader->>URLParser: Parse URL for type_tags params
URLParser-->>PageLoader: Extract {key, value, isProperty} array + cleanUrl
PageLoader->>PageLoader: Deduplicate tag names from path
loop For each unique tag name
PageLoader->>TagCache: fetchTag(name)
alt Cache hit or API success
TagCache-->>PageLoader: Tag metadata
else Error/Missing
TagCache-->>PageLoader: undefined
end
end
PageLoader->>CategoryBuilder: Map original path order to fetched tags
CategoryBuilder->>CategoryBuilder: Filter out undefined entries
CategoryBuilder-->>PageLoader: Built categories array
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@vnda/loaders/productListingPage.ts`:
- Around line 103-105: CI is failing due to formatting issues in this file
(e.g., the parseTypeTagsFromUrl function block and other nearby ranges); run the
code formatter and apply its changes—execute `deno fmt` (or your project's
formatter) on vnda/loaders/productListingPage.ts and commit the resulting
formatting changes so the parseTypeTagsFromUrl function signature, surrounding
blocks, and the other affected ranges are formatted correctly.
- Around line 117-123: cleanUrl currently retains the page query param which
causes stale pagination after filter changes; in the function that builds
cleanUrl (the block creating new URL(url.href) and before returning { typeTags,
cleanUrl }), remove the "page" search param (e.g.,
cleanUrl.searchParams.delete("page")) so that the cleanUrl passed into
toFilters(...) has pagination reset and filter navigation behaves correctly.
- Around line 153-163: The code currently builds categoryTagName from props.term
(a free-text search) and url.pathname, causing incorrect tag lookups and
unnecessary API calls; change the logic that computes
categoryTagName/uniquePathNames to use only the URL path segments (e.g.,
url.pathname.slice(1).split("/")), remove any empty segments (filter(Boolean) or
item => item !== ""), and use those filtered segments (optionally
decodeURIComponent) when calling the tags endpoint so lookups use the slug/path
rather than props.term; update occurrences of categoryTagName and
uniquePathNames to reflect this filtered-path-only source.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1a573ad4-9742-4fb7-b42c-ded65fb7d123
📒 Files selected for processing (1)
vnda/loaders/productListingPage.ts
| const parseTypeTagsFromUrl = (url: URL): { typeTags: TypeTag[]; cleanUrl: URL } => { | ||
| const TYPE_TAG_PATTERN = /^type_tags\[(.+)\]\[\]$/; | ||
|
|
There was a problem hiding this comment.
deno fmt --check is failing for this file.
CI is red due to formatting diffs in these changed ranges. Please run formatter before merge.
Also applies to: 153-157, 161-167, 333-333
🧰 Tools
🪛 GitHub Actions: ci
[error] 103-167: deno fmt --check failed for this file. Deno reported formatting diffs near lines 103-105, 153-157, and 161-167 (e.g., function signature/arguments and split .split("/" )/filter formatting).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vnda/loaders/productListingPage.ts` around lines 103 - 105, CI is failing due
to formatting issues in this file (e.g., the parseTypeTagsFromUrl function block
and other nearby ranges); run the code formatter and apply its changes—execute
`deno fmt` (or your project's formatter) on vnda/loaders/productListingPage.ts
and commit the resulting formatting changes so the parseTypeTagsFromUrl function
signature, surrounding blocks, and the other affected ranges are formatted
correctly.
| const cleanUrl = new URL(url.href); | ||
| [...cleanUrl.searchParams.keys()] | ||
| .filter((k) => k.startsWith("type_tags")) | ||
| .forEach((k) => cleanUrl.searchParams.delete(k)); | ||
|
|
||
| return { typeTags, cleanUrl }; | ||
| }; |
There was a problem hiding this comment.
Reset page in cleanUrl to preserve filter navigation behavior.
cleanUrl is passed to toFilters(...), but this parser no longer removes page. That can keep users on stale page numbers after toggling filters.
Suggested fix
const cleanUrl = new URL(url.href);
[...cleanUrl.searchParams.keys()]
.filter((k) => k.startsWith("type_tags"))
.forEach((k) => cleanUrl.searchParams.delete(k));
+cleanUrl.searchParams.delete("page");
return { typeTags, cleanUrl };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const cleanUrl = new URL(url.href); | |
| [...cleanUrl.searchParams.keys()] | |
| .filter((k) => k.startsWith("type_tags")) | |
| .forEach((k) => cleanUrl.searchParams.delete(k)); | |
| return { typeTags, cleanUrl }; | |
| }; | |
| const cleanUrl = new URL(url.href); | |
| [...cleanUrl.searchParams.keys()] | |
| .filter((k) => k.startsWith("type_tags")) | |
| .forEach((k) => cleanUrl.searchParams.delete(k)); | |
| cleanUrl.searchParams.delete("page"); | |
| return { typeTags, cleanUrl }; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vnda/loaders/productListingPage.ts` around lines 117 - 123, cleanUrl
currently retains the page query param which causes stale pagination after
filter changes; in the function that builds cleanUrl (the block creating new
URL(url.href) and before returning { typeTags, cleanUrl }), remove the "page"
search param (e.g., cleanUrl.searchParams.delete("page")) so that the cleanUrl
passed into toFilters(...) has pagination reset and filter navigation behaves
correctly.
| const categoryTagName = (props.term || url.pathname.slice(1) || "").split("/"); | ||
|
|
||
| const properties1 = url.searchParams.getAll("type_tags[property1][]"); | ||
| const properties2 = url.searchParams.getAll("type_tags[property2][]"); | ||
| const properties3 = url.searchParams.getAll("type_tags[property3][]"); | ||
|
|
||
| const categoryTagNames = Array.from(url.searchParams.values()); | ||
| const uniquePathNames = [ | ||
| ...new Set( | ||
| categoryTagName.filter((item): item is string => typeof item === "string"), | ||
| ), | ||
| ]; |
There was a problem hiding this comment.
Use slug/path for category tag lookup, not props.term, and drop empty segments.
This segment currently derives tag names from props.term, which can be a free-text search term and trigger unnecessary /api/v2/tags/:name calls. Also, empty segments are not filtered before fetch.
Suggested fix
-const categoryTagName = (props.term || url.pathname.slice(1) || "").split("/");
+const categoryTagName = (props.slug || url.pathname.slice(1) || "").split("/");
const uniquePathNames = [
...new Set(
- categoryTagName.filter((item): item is string => typeof item === "string"),
+ categoryTagName.filter(
+ (item): item is string => typeof item === "string" && item.trim().length > 0,
+ ),
),
];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const categoryTagName = (props.term || url.pathname.slice(1) || "").split("/"); | |
| const properties1 = url.searchParams.getAll("type_tags[property1][]"); | |
| const properties2 = url.searchParams.getAll("type_tags[property2][]"); | |
| const properties3 = url.searchParams.getAll("type_tags[property3][]"); | |
| const categoryTagNames = Array.from(url.searchParams.values()); | |
| const uniquePathNames = [ | |
| ...new Set( | |
| categoryTagName.filter((item): item is string => typeof item === "string"), | |
| ), | |
| ]; | |
| const categoryTagName = (props.slug || url.pathname.slice(1) || "").split("/"); | |
| const properties1 = url.searchParams.getAll("type_tags[property1][]"); | |
| const properties2 = url.searchParams.getAll("type_tags[property2][]"); | |
| const properties3 = url.searchParams.getAll("type_tags[property3][]"); | |
| const uniquePathNames = [ | |
| ...new Set( | |
| categoryTagName.filter( | |
| (item): item is string => typeof item === "string" && item.trim().length > 0, | |
| ), | |
| ), | |
| ]; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vnda/loaders/productListingPage.ts` around lines 153 - 163, The code
currently builds categoryTagName from props.term (a free-text search) and
url.pathname, causing incorrect tag lookups and unnecessary API calls; change
the logic that computes categoryTagName/uniquePathNames to use only the URL path
segments (e.g., url.pathname.slice(1).split("/")), remove any empty segments
(filter(Boolean) or item => item !== ""), and use those filtered segments
(optionally decodeURIComponent) when calling the tags endpoint so lookups use
the slug/path rather than props.term; update occurrences of categoryTagName and
uniquePathNames to reflect this filtered-path-only source.
There was a problem hiding this comment.
2 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="vnda/loaders/productListingPage.ts">
<violation number="1" location="vnda/loaders/productListingPage.ts:117">
P2: `cleanUrl` only strips `type_tags` params but retains `page`. When passed to `toFilters(...)`, the generated filter toggle URLs will carry a stale page number, so toggling a filter may land the user on page N instead of page 1. Consider also deleting `page` from `cleanUrl`.</violation>
<violation number="2" location="vnda/loaders/productListingPage.ts:161">
P2: The filter `typeof item === "string"` does not exclude empty strings. A trailing slash in the pathname (e.g., `/feminino/`) produces an empty segment that passes through and triggers a wasted `fetchTag(api, "")` call. Filter with `item.length > 0` as well.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| }; | ||
| }); | ||
|
|
||
| const cleanUrl = new URL(url.href); |
There was a problem hiding this comment.
P2: cleanUrl only strips type_tags params but retains page. When passed to toFilters(...), the generated filter toggle URLs will carry a stale page number, so toggling a filter may land the user on page N instead of page 1. Consider also deleting page from cleanUrl.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vnda/loaders/productListingPage.ts, line 117:
<comment>`cleanUrl` only strips `type_tags` params but retains `page`. When passed to `toFilters(...)`, the generated filter toggle URLs will carry a stale page number, so toggling a filter may land the user on page N instead of page 1. Consider also deleting `page` from `cleanUrl`.</comment>
<file context>
@@ -87,6 +86,42 @@ const handleOperator = (
+ };
+ });
+
+ const cleanUrl = new URL(url.href);
+ [...cleanUrl.searchParams.keys()]
+ .filter((k) => k.startsWith("type_tags"))
</file context>
| const categoryTagNames = Array.from(url.searchParams.values()); | ||
| const uniquePathNames = [ | ||
| ...new Set( | ||
| categoryTagName.filter((item): item is string => typeof item === "string"), |
There was a problem hiding this comment.
P2: The filter typeof item === "string" does not exclude empty strings. A trailing slash in the pathname (e.g., /feminino/) produces an empty segment that passes through and triggers a wasted fetchTag(api, "") call. Filter with item.length > 0 as well.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vnda/loaders/productListingPage.ts, line 161:
<comment>The filter `typeof item === "string"` does not exclude empty strings. A trailing slash in the pathname (e.g., `/feminino/`) produces an empty segment that passes through and triggers a wasted `fetchTag(api, "")` call. Filter with `item.length > 0` as well.</comment>
<file context>
@@ -108,44 +143,40 @@ const searchLoader = async (
- const categoryTagNames = Array.from(url.searchParams.values());
+ const uniquePathNames = [
+ ...new Set(
+ categoryTagName.filter((item): item is string => typeof item === "string"),
+ ),
+ ];
</file context>
| categoryTagName.filter((item): item is string => typeof item === "string"), | |
| categoryTagName.filter((item): item is string => typeof item === "string" && item.length > 0), |
Contexto
O loader
productListingPage.tsdisparava um número excessivo de chamadas à API/api/v2/tags/:nameda VNDA por page load, resultando em erros HTTP 429 (Rate Limit) bloqueados pelo Cloudflare (Error 1015 — You are being rate limited) em páginas de categoria com filtros ativos.O erro era intermitente em produção e se tornava consistente em momentos de tráfego elevado ou em URLs com muitos filtros aplicados simultaneamente.
Diagnóstico
Causa raiz #1 — coleta indiscriminada de query params
Para a URL real que gerou o 429:
/feminino/tenis-para-corrida/amortecimento/busca/hoka_one_one?dir=asc&order=position&type_tags[Peso do tênis (g)][]=esp-peso-tenis-281&type_tags[Finalidade][]=finalidade-corrida&type_tags[Tipo de Pisada][]=esp-pisada-neutra-tipo&type_tags[Tipo de Pisada][]=esp-pisada-supinada-severa-tipo&type_tags[Artigo][]=artigo-tenis&type_tags[property1][]=38
O código original disparava 15 chamadas à
/api/v2/tags/:name:ascdir=ascpositionorder=position38type_tags[property1][]=38esp-peso-tenis-281type_tags[...][]feminino,tenis-para-corrida, ...Causa raiz #2 — sem deduplicação
O mesmo slug podia aparecer tanto em
categoryTagNamesquanto emcategoryTagName, gerando chamadas duplicadas para o mesmo recurso sem nenhum mecanismo de cache entre elas.Causa raiz #3 —
typeTagExtractordependia de chamadas desnecessáriasA função
typeTagExtractor(url, filteredTags)recebia objetosTagresolvidos dos paramstype_tags[X][]=valuepara mapear os filtros da URL noproducts/search. Porém, a URL já contém todas as informações necessárias:type_tags[Finalidade][]finalidade-corridatype_tags[property1][]A chamada à API servia apenas como validação, não como enriquecimento de dados.
Solução
1. Substituição de
typeTagExtractorporparseTypeTagsFromUrlA nova função extrai
typeTagsecleanUrldiretamente da URL, sem nenhuma chamada à API:Por que é seguro: a URL
type_tags[Finalidade][]=finalidade-corridajá encoda explicitamente chave e valor. O único comportamento quetypeTagExtractoradicionava era rejeitar slugs inválidos — slugs inválidos na URL simplesmente não retornam produtos noproducts/search, sem impacto funcional.2. Fetches de tags restritos ao pathname
3. Deduplicação via
Setnew Set()antes doPromise.allgarante que slugs repetidos no pathname sejam resolvidos apenas uma vez.Quando
/api/v2/tags/:nameainda é chamadaA API de tags continua sendo utilizada, mas agora apenas onde o objeto
Tagcompleto é estritamente necessário: os segmentos do pathname da URL.URL: /feminino/tenis-para-corrida?type_tags[Marca][]=nike&dir=asc&page=2
/api/v2/tags/:name chamada para:
✅ "feminino" → tag.title usado no breadcrumb e SEO
✅ "tenis-para-corrida" → tag.title usado no breadcrumb e SEO
/api/v2/tags/:name NÃO chamada para:
❌ "nike" (type_tags[Marca][]) → parseTypeTagsFromUrl extrai direto da URL
❌ "asc" (dir=asc) → parâmetro de sistema, nunca é tag
❌ "2" (page=2) → parâmetro de sistema, nunca é tag
O motivo pelo qual o pathname ainda precisa da API é que
tag.title— o nome legível da categoria — não pode ser derivado do slug:tag.title(API)tenis-para-corrida"Tênis para Corrida"feminino"Feminino"amortecimento"Amortecimento"Para os filtros (
type_tags[X][]=value), a URL já é a fonte da verdade: chave, valor e flagisPropertysão todos deriváveis da estrutura do parâmetro, sem necessidade de consultar a API.Impacto em chamadas à API
/feminino/.../hoka_one_one?dir=asc&order=position&[7 filtros]/acessorios/meia-cano-curto/...?utm_source=vurdere-ai&[4 filtros]/tenis-de-corrida?type_tags[Categoria][]=...&type_tags[Marca][]=.../feminino(sem filtros)/busca?q=hokaTestes realizados localmente
Os testes foram realizados localmente comparando produção (
velocita.com.br) e ambiente de desenvolvimento via JSON-LDProductListingPageembutido no HTML, não fazem parte deste PR.Unitários —
parseTypeTagsFromUrl— ✅ 8/8 passandotype_tagssimples e com múltiplos valores no mesmo filtroisProperty: trueidentificado corretamente paraproperty1/2/3dir,order,utm_source,sort,page) não entram emtypeTagscleanUrlremove apenas paramstype_tags, preservando todos os demaisproducts/search(incluindo agrupamento por chave)E2E de regressão — ✅ 7/7 passando
/tenis-de-corridacom 2 filtros type_tagsutm_source+property1/busca?q=hoka)/feminino)Arquivos alterados
vnda/loaders/productListingPage.ts ← único arquivo modificado
Breaking changes
Nenhum. A interface pública do loader (
Props, tipos de retorno,cache,cacheKey) permanece idêntica. O comportamento observável — produtos, breadcrumb, filtros, SEO e paginação — foi validado como equivalente à versão de produção em todos os cenários de teste.Referências
Summary by cubic
Reduce VNDA tag API calls in the PLP loader to stop Cloudflare 429s on filtered category pages. We now parse
type_tagsfrom the URL and only fetch tag details for pathname segments.typeTagExtractorwithparseTypeTagsFromUrlto extract filters without API calls, returningtypeTagsandcleanUrl.fetchTag, deduplicated withSetand cached in a map./api/v2/tags/:namecalls on filtered URLs; zero calls on search pages when possible.Written for commit 99b4ec3. Summary will update on new commits.
Summary by CodeRabbit