feat: replace pagetype with by-linkid cascade + localized PLP/collection pages#3401
feat: replace pagetype with by-linkid cascade + localized PLP/collection pages#3401hellofanny wants to merge 18 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (13)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (12)
WalkthroughRewrites the VTEX collection resolution pipeline from ChangesLocalization for Collections via ByLinkId API
Sequence Diagram(s)sequenceDiagram
participant Browser
participant PLPPage as [...slug].tsx
participant GraphQL
participant CollectionLoader
participant VtexCommerce as catalog.byLinkId
participant CollectionResolver
Browser->>PLPPage: getStaticProps(slug, locale)
PLPPage->>GraphQL: Query.collection(slug, locale)
GraphQL->>CollectionResolver: mutateLocaleContext(locale)
GraphQL->>CollectionLoader: load(slug)
CollectionLoader->>VtexCommerce: byLinkId.category(leaf)
VtexCommerce-->>CollectionLoader: ByLinkIdCategoryRoot | null
CollectionLoader->>VtexCommerce: byLinkId.brand(leaf) [if null]
VtexCommerce-->>CollectionLoader: ByLinkIdBrandRoot | null
CollectionLoader-->>GraphQL: Root
GraphQL->>CollectionResolver: otherLocales(Root)
CollectionResolver->>CollectionLoader: load(each segment)
CollectionResolver-->>GraphQL: [{locale, slug}]
GraphQL-->>PLPPage: {collection: {otherLocales: [...]}}
PLPPage-->>Browser: LocalizedProductProvider(otherLocales, urlSuffix="")
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (7)
packages/api/src/platforms/vtex/resolvers/collection.ts (3)
112-115: ⚖️ Poor tradeoffPerformance: category tree fetched on every category meta resolution.
commerce.catalog.category.tree(10)is called for each category-based collection'smetaresolver. If the tree is large, this could add latency. The DataLoader caches collection roots, but the tree itself isn't cached request-scoped.Consider caching the tree in
ctx.storagesimilar toproductTranslationsCache, or verify that upstream HTTP caching mitigates this.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/platforms/vtex/resolvers/collection.ts` around lines 112 - 115, The category tree is being fetched from `commerce.catalog.category.tree(10)` on every resolution of the collection meta resolver without request-scoped caching, which can cause performance issues for large trees. Implement request-scoped caching for the category tree by checking if it exists in `ctx.storage` before calling `commerce.catalog.category.tree(10)`, and if it doesn't exist, fetch it and store the result in `ctx.storage` for subsequent uses within the same request. Follow the same pattern used for `productTranslationsCache` to maintain consistency with existing caching patterns in the codebase.
191-193: ⚡ Quick winSilent error swallowing may hide legitimate failures.
The
catchblock returnsnullfor any error, including 5xx server errors or network issues. Consider logging the error for observability, or only swallowing expected errors (e.g., NotFoundError).} catch (err) { + // Log unexpected errors for debugging while gracefully degrading + console.warn('Failed to load collection entities for otherLocales:', err) return null }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/platforms/vtex/resolvers/collection.ts` around lines 191 - 193, The catch block in the collection resolver is silently swallowing all errors without any logging or visibility into what went wrong. Add logging for the caught error before returning null to improve observability, or alternatively, make the catch block more specific by only catching expected errors like NotFoundError and allowing other errors (such as server errors or network issues) to propagate up. This ensures that unexpected failures are not hidden and can be properly debugged.
157-162: ⚡ Quick winType safety: repeated
as anycasts for discoveryConfig.Multiple
(ctx.discoveryConfig as any)casts bypass TypeScript's type checking. Consider defining a proper interface for the localization config shape and using a type guard or helper function.interface LocalizationConfig { enabled?: boolean defaultLocale?: string locales?: Record<string, unknown> } function getLocalizationConfig(ctx: GraphqlContext): LocalizationConfig | null { return (ctx.discoveryConfig as { localization?: LocalizationConfig })?.localization ?? null }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/platforms/vtex/resolvers/collection.ts` around lines 157 - 162, The code contains repeated `as any` type casts for accessing ctx.discoveryConfig properties (specifically checking isLocalizationEnabled and accessing configuredLocales), which bypasses TypeScript type safety. Define a proper LocalizationConfig interface with typed properties for enabled, defaultLocale, and locales, then create a helper function (such as getLocalizationConfig) that safely extracts and returns the typed localization configuration from ctx.discoveryConfig. Replace all instances of the unsafe `(ctx.discoveryConfig as any)?.localization` casts throughout this resolver with calls to the new helper function or properly typed property access to restore type safety.packages/api/src/platforms/vtex/loaders/collection.ts (1)
63-63: 💤 Low valueConsider defensive handling for edge case.
The
.at(-1)!assertion is technically safe sincesplit('/')always returns at least one element, but an empty slug would produce an emptylastSegment, which then queries the API with an empty string. Consider adding an early guard if this edge case should fail fast.+ if (!normalizedSlug) { + throw new NotFoundError(`Empty slug provided.`) + } + const lastSegment = normalizedSlug.split('/').at(-1)!🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/platforms/vtex/loaders/collection.ts` at line 63, The lastSegment variable is extracted from normalizedSlug using split('/').at(-1)! without validating that the resulting segment is not empty, which could cause an API query with an empty string if the slug is empty or ends with a slash. Add an early guard check after extracting lastSegment to validate that it is not an empty string, and return or throw an error appropriately if it is empty. This ensures the function fails fast rather than proceeding with invalid data to the API.packages/api/src/platforms/vtex/resolvers/product.ts (1)
108-127: ⚡ Quick winDRY: Extract shared cache-or-fetch logic.
The pattern for fetching and caching localized product data is duplicated between
breadcrumbList(lines 108-127) andotherLocales(lines 302-318). Extract to a helper function to reduce duplication and ensure consistent behavior.async function getLocalizedProductEntry( ctx: GraphqlContext, productId: string, locale: string ): Promise<LocalizedProductEntry | null> { const cacheKey = `${productId}:${locale}` let entry = ctx.storage.productTranslationsCache?.get(cacheKey) if (!entry) { try { const result = await ctx.clients.catalog.getLocalizedProduct(productId, locale) entry = { linkId: result.linkId, categories: result.categories ?? [], availableLinkIds: result.availableLinkIds ?? {}, } ctx.storage.productTranslationsCache ??= new Map() ctx.storage.productTranslationsCache.set(cacheKey, entry) } catch { return null } } return entry }Also applies to: 302-318
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/platforms/vtex/resolvers/product.ts` around lines 108 - 127, Extract the duplicated cache-or-fetch logic used in both the breadcrumbList and otherLocales resolvers into a new helper function called getLocalizedProductEntry that accepts ctx, productId, and locale parameters. This function should handle cache lookup, call ctx.clients.catalog.getLocalizedProduct, cache the result with linkId, categories, and availableLinkIds properties, and return either the cached/fetched entry or null on error. Replace the duplicated try-catch blocks in both resolvers with calls to this new helper function to ensure consistent behavior and reduce code duplication.packages/api/src/platforms/vtex/resolvers/query.ts (1)
100-140: 💤 Low valueComplex but well-documented localized slug validation.
The logic for validating localized slugs against the Catalog Dataplane is thorough. The cache-or-fetch pattern here would also benefit from the helper extraction suggested for the product resolver.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/api/src/platforms/vtex/resolvers/query.ts` around lines 100 - 140, The cache-or-fetch pattern for retrieving localized product data is complex and would benefit from extraction into a reusable helper function. Extract the logic that checks ctx.storage.productTranslationsCache, retrieves or creates an entry using catalog.getLocalizedProduct, and sets the cache into a separate helper function (similar to the pattern suggested for the product resolver). This helper should encapsulate the cacheKey generation, cache lookup, the conditional call to getLocalizedProduct when entry is not found, and the cache storage logic to improve code reusability and maintainability.packages/core/src/sdk/localization/useBindingSelector.ts (1)
69-70: ⚡ Quick winValidate recovered session payload before using it
Line 69 asserts parsed JSON as
LocalizedProductLocale[]. SincesessionStorageis untyped, add a runtime guard and returnnullfor invalid shapes instead of asserting.Suggested patch
+function isLocalizedProductLocaleArray( + value: unknown +): value is LocalizedProductLocale[] { + return ( + Array.isArray(value) && + value.every((item) => { + if (typeof item !== 'object' || item === null) return false + return ( + typeof Reflect.get(item, 'locale') === 'string' && + typeof Reflect.get(item, 'slug') === 'string' + ) + }) + ) +} + export function recoverOtherLocales(): LocalizedProductLocale[] | null { if (typeof window === 'undefined') return null @@ - return raw ? (JSON.parse(raw) as LocalizedProductLocale[]) : null + if (!raw) return null + const parsed: unknown = JSON.parse(raw) + return isLocalizedProductLocaleArray(parsed) ? parsed : null } catch { return null } }As per coding guidelines, "Ensure type safety and avoid type assertions when possible".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/sdk/localization/useBindingSelector.ts` around lines 69 - 70, The code at the JSON.parse line uses a type assertion to cast the parsed JSON as LocalizedProductLocale[] without validating the actual shape of the data, which is unsafe since sessionStorage is untyped. Add a runtime validation function that checks whether the parsed object actually matches the LocalizedProductLocale[] structure before returning it. If the parsed data fails validation, return null instead of relying on the type assertion. This ensures type safety by catching invalid data shapes at runtime rather than blindly trusting the assertion.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/api/src/platforms/vtex/loaders/collection.ts`:
- Around line 66-77: The code in the collection.ts file defers implementing
fatherCategoryId-based disambiguation when multiple categories share the same
linkId, simply taking the first match. Create a tracking issue in your project
management system to ensure this edge case is addressed in a follow-up
implementation. The issue should reference that fatherCategoryId (available in
the VTEX catalog response) can disambiguate categories at different tree levels,
and note that similar disambiguation logic is already implemented elsewhere in
resolvers/collection.ts. This prevents the defer from being forgotten and
provides context for future work.
In `@packages/api/src/platforms/vtex/resolvers/query.ts`:
- Line 352: The URL parsing operation using new URL(node.url) in the slug
assignment can throw an exception if the URL is malformed, which would crash the
entire allCollections resolver. Wrap the URL parsing logic (where slug is being
assigned with new URL(node.url).pathname.slice(1).toLowerCase()) in a try/catch
block to handle malformed URLs gracefully. When a URL parsing error occurs,
either skip that node/entry, provide a fallback slug value, or log the error
appropriately so the resolver can continue processing other collection entries
without crashing.
In `@packages/api/src/platforms/vtex/typeDefs/query.graphql`:
- Around line 233-239: The locale argument in the query parameters is accepted
and propagated to platform APIs without validation, which violates input
validation guidelines. Before the resolver uses the locale parameter in
mutateLocaleContext() or propagates it to downstream clients (Search API,
Catalog API, OrderForm), validate it against the
discoveryConfig?.localization?.locales array to ensure it is a configured
locale. Apply the same validation pattern that is already implemented in the
otherLocales resolvers to maintain consistency across the codebase and prevent
invalid locale values from reaching platform APIs.
In `@packages/core/src/pages/`[slug]/p.tsx:
- Around line 134-143: Remove the unnecessary type assertions from the hreflang
configuration extraction in the section extracting locales and binding URLs.
Specifically, delete the `as Record<string, any>` assertion from the locales
variable assignment, the `as | string | undefined` assertion from the
defaultLocale variable assignment, and the `as | string | undefined` assertion
from the bindingUrl variable assignment. These properties are already properly
typed in the codebase and the assertions only mask type information, preventing
compile-time detection of type changes.
In `@packages/core/src/sdk/localization/useBindingSelector.ts`:
- Around line 39-42: The storage key derivation at line 41 depends on array
ordering of otherLocales and the persistence logic at lines 171-175 applies to
both PLP and PDP flows. Instead of extracting skuId from
otherLocales[0]?.slug.split('-').pop(), extract it from window.location.pathname
to make it stable and independent of array ordering. Additionally, modify the
persistence condition around lines 171-175 to only persist when urlSuffix equals
'/p' (PDP flow) rather than persisting whenever otherLocales exists, ensuring
the binding selector state is only cached for product detail pages.
---
Nitpick comments:
In `@packages/api/src/platforms/vtex/loaders/collection.ts`:
- Line 63: The lastSegment variable is extracted from normalizedSlug using
split('/').at(-1)! without validating that the resulting segment is not empty,
which could cause an API query with an empty string if the slug is empty or ends
with a slash. Add an early guard check after extracting lastSegment to validate
that it is not an empty string, and return or throw an error appropriately if it
is empty. This ensures the function fails fast rather than proceeding with
invalid data to the API.
In `@packages/api/src/platforms/vtex/resolvers/collection.ts`:
- Around line 112-115: The category tree is being fetched from
`commerce.catalog.category.tree(10)` on every resolution of the collection meta
resolver without request-scoped caching, which can cause performance issues for
large trees. Implement request-scoped caching for the category tree by checking
if it exists in `ctx.storage` before calling
`commerce.catalog.category.tree(10)`, and if it doesn't exist, fetch it and
store the result in `ctx.storage` for subsequent uses within the same request.
Follow the same pattern used for `productTranslationsCache` to maintain
consistency with existing caching patterns in the codebase.
- Around line 191-193: The catch block in the collection resolver is silently
swallowing all errors without any logging or visibility into what went wrong.
Add logging for the caught error before returning null to improve observability,
or alternatively, make the catch block more specific by only catching expected
errors like NotFoundError and allowing other errors (such as server errors or
network issues) to propagate up. This ensures that unexpected failures are not
hidden and can be properly debugged.
- Around line 157-162: The code contains repeated `as any` type casts for
accessing ctx.discoveryConfig properties (specifically checking
isLocalizationEnabled and accessing configuredLocales), which bypasses
TypeScript type safety. Define a proper LocalizationConfig interface with typed
properties for enabled, defaultLocale, and locales, then create a helper
function (such as getLocalizationConfig) that safely extracts and returns the
typed localization configuration from ctx.discoveryConfig. Replace all instances
of the unsafe `(ctx.discoveryConfig as any)?.localization` casts throughout this
resolver with calls to the new helper function or properly typed property access
to restore type safety.
In `@packages/api/src/platforms/vtex/resolvers/product.ts`:
- Around line 108-127: Extract the duplicated cache-or-fetch logic used in both
the breadcrumbList and otherLocales resolvers into a new helper function called
getLocalizedProductEntry that accepts ctx, productId, and locale parameters.
This function should handle cache lookup, call
ctx.clients.catalog.getLocalizedProduct, cache the result with linkId,
categories, and availableLinkIds properties, and return either the
cached/fetched entry or null on error. Replace the duplicated try-catch blocks
in both resolvers with calls to this new helper function to ensure consistent
behavior and reduce code duplication.
In `@packages/api/src/platforms/vtex/resolvers/query.ts`:
- Around line 100-140: The cache-or-fetch pattern for retrieving localized
product data is complex and would benefit from extraction into a reusable helper
function. Extract the logic that checks ctx.storage.productTranslationsCache,
retrieves or creates an entry using catalog.getLocalizedProduct, and sets the
cache into a separate helper function (similar to the pattern suggested for the
product resolver). This helper should encapsulate the cacheKey generation, cache
lookup, the conditional call to getLocalizedProduct when entry is not found, and
the cache storage logic to improve code reusability and maintainability.
In `@packages/core/src/sdk/localization/useBindingSelector.ts`:
- Around line 69-70: The code at the JSON.parse line uses a type assertion to
cast the parsed JSON as LocalizedProductLocale[] without validating the actual
shape of the data, which is unsafe since sessionStorage is untyped. Add a
runtime validation function that checks whether the parsed object actually
matches the LocalizedProductLocale[] structure before returning it. If the
parsed data fails validation, return null instead of relying on the type
assertion. This ensures type safety by catching invalid data shapes at runtime
rather than blindly trusting the assertion.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 95c0a81b-a9e4-4fe6-bd9f-8b9d23c16492
⛔ Files ignored due to path filters (3)
packages/api/src/__generated__/schema.tsis excluded by!**/__generated__/**and included bypackages/**packages/core/@generated/gql.tsis excluded by!**/@generated/**,!**/@generated/**and included bypackages/**packages/core/@generated/graphql.tsis excluded by!**/@generated/**,!**/@generated/**and included bypackages/**
📒 Files selected for processing (23)
packages/api/src/platforms/vtex/clients/catalog/index.tspackages/api/src/platforms/vtex/clients/commerce/index.tspackages/api/src/platforms/vtex/clients/commerce/types/ByLinkId.tspackages/api/src/platforms/vtex/clients/index.tspackages/api/src/platforms/vtex/index.tspackages/api/src/platforms/vtex/loaders/collection.tspackages/api/src/platforms/vtex/resolvers/collection.tspackages/api/src/platforms/vtex/resolvers/product.tspackages/api/src/platforms/vtex/resolvers/query.tspackages/api/src/platforms/vtex/typeDefs/collection.graphqlpackages/api/src/platforms/vtex/typeDefs/product.graphqlpackages/api/src/platforms/vtex/typeDefs/query.graphqlpackages/core/src/components/sections/EmptyState/EmptyState.tsxpackages/core/src/components/templates/ProductListingPage/ProductListingPage.tsxpackages/core/src/components/ui/Breadcrumb/Breadcrumb.tsxpackages/core/src/components/ui/LocalizationButton/LocalizationButton.tsxpackages/core/src/customizations/src/fragments/ServerCollectionPage.tspackages/core/src/experimental/index.tspackages/core/src/pages/[...slug].tsxpackages/core/src/pages/[slug]/p.tsxpackages/core/src/sdk/localization/LocalizedProductContext.tsxpackages/core/src/sdk/localization/useBindingSelector.tspackages/core/test/sdk/localization/useBindingSelector.test.tsx
| const categories = | ||
| await clients.commerce.catalog.byLinkId.category(lastSegment) | ||
| if (categories !== null && categories.length > 0) { | ||
| // When multiple categories share the same linkId at different tree levels | ||
| // (e.g. "bolas" under both "esportes" and "infantil"), fatherCategoryId-based | ||
| // disambiguation can be added here in a follow-up. For now, take the first match. | ||
| return { | ||
| ...categories[0], | ||
| entityType: 'category' as const, | ||
| slug: normalizedSlug, | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there's a tracking issue for this disambiguation work
rg -n "fatherCategoryId" --type=ts -C2Repository: vtex/faststore
Length of output: 2818
Consider tracking fatherCategoryId-based disambiguation for follow-up.
The code currently selects the first category when multiple share the same linkId at different tree levels. The fatherCategoryId field (available in the response) could disambiguate these cases—it's already used elsewhere for similar category level distinction (see resolvers/collection.ts). While the comment correctly flags this as deferred, verify whether this edge case warrants a tracking issue to prevent it from being overlooked.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/api/src/platforms/vtex/loaders/collection.ts` around lines 66 - 77,
The code in the collection.ts file defers implementing fatherCategoryId-based
disambiguation when multiple categories share the same linkId, simply taking the
first match. Create a tracking issue in your project management system to ensure
this edge case is addressed in a follow-up implementation. The issue should
reference that fatherCategoryId (available in the VTEX catalog response) can
disambiguate categories at different tree levels, and note that similar
disambiguation logic is already implemented elsewhere in
resolvers/collection.ts. This prevents the defer from being forgotten and
provides context for future work.
| metaTagDescription: node.MetaTagDescription, | ||
| availableLinkIds: null, | ||
| entityType: 'category' as const, | ||
| slug: new URL(node.url).pathname.slice(1).toLowerCase(), |
There was a problem hiding this comment.
Defensive: wrap URL parsing in try/catch.
new URL(node.url) can throw if the URL is malformed. While VTEX API data should be valid, a single corrupted entry would crash the entire allCollections resolver.
- slug: new URL(node.url).pathname.slice(1).toLowerCase(),
+ slug: (() => {
+ try {
+ return new URL(node.url).pathname.slice(1).toLowerCase()
+ } catch {
+ return slugify(node.name)
+ }
+ })(),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/api/src/platforms/vtex/resolvers/query.ts` at line 352, The URL
parsing operation using new URL(node.url) in the slug assignment can throw an
exception if the URL is malformed, which would crash the entire allCollections
resolver. Wrap the URL parsing logic (where slug is being assigned with new
URL(node.url).pathname.slice(1).toLowerCase()) in a try/catch block to handle
malformed URLs gracefully. When a URL parsing error occurs, either skip that
node/entry, provide a fallback slug value, or log the error appropriately so the
resolver can continue processing other collection entries without crashing.
| """ | ||
| Locale code (e.g. "pt-BR"). When provided, the resolver sets the request | ||
| locale so that localization-aware fields (e.g. otherLocales) return data | ||
| for the correct locale instead of the store default. | ||
| """ | ||
| locale: String | ||
| ): StoreCollection! |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Inspect Query.collection resolver and locale handling:"
sed -n '1,260p' packages/api/src/platforms/vtex/resolvers/query.ts
echo
echo "Find locale usage in VTEX resolvers and clients:"
rg -n -C3 '\blocale\b|ctx\.storage\.locale|getLocalizedProduct|Accept-Language' packages/api/src/platforms/vtex/resolvers packages/api/src/platforms/vtex/clients
echo
echo "Look for locale validation/allowlist patterns:"
rg -n -C2 'supportedLocales|defaultLocale|localization|validate|isValidLocale|Intl\.Locale|BCP|^[a-z]{2}-[A-Z]{2}$' packages/api/src/platforms/vtex/resolvers/query.ts packages/api/src/platforms/vtex/index.tsRepository: vtex/faststore
Length of output: 40014
🏁 Script executed:
find packages/api/src/platforms/vtex -name "*contex*" -o -name "*context*" | head -20Repository: vtex/faststore
Length of output: 106
🏁 Script executed:
rg -n "mutateLocaleContext|findLocale" packages/api/src/platforms/vtex/utils/ -A 15Repository: vtex/faststore
Length of output: 831
🏁 Script executed:
cat -n packages/api/src/platforms/vtex/utils/facets.ts | sed -n '1,100p'Repository: vtex/faststore
Length of output: 3402
🏁 Script executed:
cat -n packages/api/src/platforms/vtex/utils/contex.tsRepository: vtex/faststore
Length of output: 495
🏁 Script executed:
rg -n "discoveryConfig.*locale|configuredLocales|supportedLocales" packages/api/src/platforms/vtex/ -A 2 -B 2Repository: vtex/faststore
Length of output: 2540
Validate locale argument against configured locales before propagating to platform APIs.
The locale: String argument (line 238) is accepted directly and passed to mutateLocaleContext() without server-side validation. The resolver then propagates it unchecked to downstream clients (Search API as query parameter, Catalog API as Accept-Language header, and OrderForm as clientPreferencesData.locale).
This violates the coding guideline requiring validation of customer input before platform propagation. Validate the input against discoveryConfig?.localization?.locales (available in discovery config) before setting ctx.storage.locale, matching the validation pattern already used in otherLocales resolvers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/api/src/platforms/vtex/typeDefs/query.graphql` around lines 233 -
239, The locale argument in the query parameters is accepted and propagated to
platform APIs without validation, which violates input validation guidelines.
Before the resolver uses the locale parameter in mutateLocaleContext() or
propagates it to downstream clients (Search API, Catalog API, OrderForm),
validate it against the discoveryConfig?.localization?.locales array to ensure
it is a configured locale. Apply the same validation pattern that is already
implemented in the otherLocales resolvers to maintain consistency across the
codebase and prevent invalid locale values from reaching platform APIs.
Source: Coding guidelines
| const locales = storeConfig.localization.locales as Record<string, any> | ||
| const baseStoreUrl = storeConfig.storeUrl.replace(/\/$/, '') | ||
| const defaultLocale = storeConfig.localization.defaultLocale as | ||
| | string | ||
| | undefined | ||
|
|
||
| for (const { locale, slug } of server.product.otherLocales) { | ||
| const bindingUrl = locales?.[locale]?.bindings?.[0]?.url as | ||
| | string | ||
| | undefined |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find discovery config declarations/typing sources
fd -i "discovery.config*"
# Inspect localization-related typing across TS files
rg -n --type=ts "declare module ['\"]discovery.config['\"]|localization|defaultLocale|bindings|storeUrl"
# Confirm assertion usage in the changed PDP file
rg -n --type=ts "as Record<string, any>|as\\s*\\|\\s*string\\s*\\|\\s*undefined" "packages/core/src/pages/[slug]/p.tsx"Repository: vtex/faststore
Length of output: 44239
🏁 Script executed:
cat -n packages/core/src/typings/locales.tsRepository: vtex/faststore
Length of output: 1084
🏁 Script executed:
cat -n packages/core/src/sdk/localization/types.tsRepository: vtex/faststore
Length of output: 1242
🏁 Script executed:
head -200 packages/core/src/pages/\[slug\]/p.tsx | tail -100Repository: vtex/faststore
Length of output: 3306
🏁 Script executed:
rg -n "storeConfig.*:" packages/core/src -A 5 -B 2 | head -50Repository: vtex/faststore
Length of output: 3845
🏁 Script executed:
rg -n "type StoreConfig|interface StoreConfig" packages/core/src -A 20 | head -80Repository: vtex/faststore
Length of output: 5153
🏁 Script executed:
grep -n "storeConfig" packages/core/src/pages/\[slug\]/p.tsx | head -20Repository: vtex/faststore
Length of output: 650
🏁 Script executed:
cat -n packages/core/discovery.config.default.js | head -100Repository: vtex/faststore
Length of output: 3160
🏁 Script executed:
rg -n "localization|locales" packages/core/src/sdk/localization/useLocalizationConfig.tsx -A 3 -B 2Repository: vtex/faststore
Length of output: 668
Remove unnecessary type assertions in hreflang config extraction.
Lines 134, 136–138, and 141–142 use as casts that obscure properly typed configuration. The locales, defaultLocale, and bindingUrl properties are already strongly typed in the codebase and do not require assertions. Removing them exposes type changes at compile time instead of masking them.
Suggested refactor
- const locales = storeConfig.localization.locales as Record<string, any>
+ const locales = storeConfig.localization.locales
const baseStoreUrl = storeConfig.storeUrl.replace(/\/$/, '')
- const defaultLocale = storeConfig.localization.defaultLocale as
- | string
- | undefined
+ const defaultLocale = storeConfig.localization.defaultLocale
@@
- const bindingUrl = locales?.[locale]?.bindings?.[0]?.url as
- | string
- | undefined
- if (bindingUrl) {
+ const bindingUrl = locales?.[locale]?.bindings?.[0]?.url
+ if (typeof bindingUrl === 'string' && bindingUrl.length > 0) {
hreflangLinks.push({
rel: 'alternate',
hrefLang: locale,
href: `${bindingUrl.replace(/\/$/, '')}/${slug}/p`,
})
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/pages/`[slug]/p.tsx around lines 134 - 143, Remove the
unnecessary type assertions from the hreflang configuration extraction in the
section extracting locales and binding URLs. Specifically, delete the `as
Record<string, any>` assertion from the locales variable assignment, the `as |
string | undefined` assertion from the defaultLocale variable assignment, and
the `as | string | undefined` assertion from the bindingUrl variable assignment.
These properties are already properly typed in the codebase and the assertions
only mask type information, preventing compile-time detection of type changes.
Source: Coding guidelines
| if (typeof window === 'undefined' || !otherLocales.length) return | ||
|
|
||
| const skuId = otherLocales[0]?.slug.split('-').pop() | ||
| if (!skuId) return |
There was a problem hiding this comment.
Scope session persistence to PDPs and key by current pathname SKU
Line 41 derives the storage key from the first localized slug, and Lines 171-175 persist whenever otherLocales exists (including PLP). This can create irrelevant keys and makes keying depend on array ordering. Derive the key from window.location.pathname and persist only for PDP flow (urlSuffix === '/p').
Suggested patch
export function persistOtherLocales(
otherLocales: LocalizedProductLocale[]
): void {
if (typeof window === 'undefined' || !otherLocales.length) return
- const skuId = otherLocales[0]?.slug.split('-').pop()
+ const skuId = getSkuIdFromPdpPath(window.location.pathname)
if (!skuId) return
try {
@@
useEffect(() => {
- if (otherLocales?.length) {
+ if (urlSuffix === '/p' && otherLocales?.length) {
persistOtherLocales(otherLocales)
}
- }, [otherLocales])
+ }, [otherLocales, urlSuffix])Also applies to: 168-175
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/sdk/localization/useBindingSelector.ts` around lines 39 -
42, The storage key derivation at line 41 depends on array ordering of
otherLocales and the persistence logic at lines 171-175 applies to both PLP and
PDP flows. Instead of extracting skuId from
otherLocales[0]?.slug.split('-').pop(), extract it from window.location.pathname
to make it stable and independent of array ordering. Additionally, modify the
persistence condition around lines 171-175 to only persist when urlSuffix equals
'/p' (PDP flow) rather than persisting whenever otherLocales exists, ensuring
the binding selector state is only cached for product detail pages.
LocalizationButton lives in the Navbar (global section) uses useSafePDP to have product (localized slugs) on non-PDP pages
instead of splitting fullPathUriName
…ution
Adds catalog.byLinkId.{category,brand,collection} methods to the
VtexCommerce client
Replaces the pagetype API with the typed by-linkid cascade (category → brand → collection). Injects entityType discriminator and slug into loader results
0424a04 to
7f6d686
Compare
@faststore/api
@faststore/cli
@faststore/components
@faststore/core
@faststore/diagnostics
@faststore/lighthouse
@faststore/sdk
@faststore/ui
commit: |
Summary
pagetypewithby-linkidcascade for all stores inQuery.collectionandcollectionLoader. The cascade triescategory/by-linkid→brand/by-linkid→collection/by-linkidin sequence; 404 advances to the next step, 5xx is a hard failure.pagetypeis retired on this code path.StoreCollection.otherLocales— a new lazy GraphQL field built fromavailableLinkIdsin theby-linkidresponse, enabling theLocalizationSelectorto navigate to the same collection in another locale.StoreProduct.otherLocalesand uses the Catalog Dataplane API to resolve localized product slugs and breadcrumbs for PDP pages (localization.enabledstores only).by-linkidcalls, preserving parity withpagetype's case-insensitive behavior (merchants can register mixed-caselinkIdvalues via the Catalog multilanguage API).StoreCollection.metafor categories now fetches the category tree to derive canonical (default-locale) slugs forselectedFacets, sincecategory/by-linkidechoes the queried (potentially localized) slug inlinkIdrather than the canonical slug.What changed
@faststore/apicatalog.byLinkId.{category,brand,collection}client methodscollectionLoader: replacedpagetypewith theby-linkidcascade; addedentityTypediscriminator; slug normalization (toLowerCase)StoreCollectionresolvers:meta(async, canonical slugs via category tree),breadcrumbList,seo,type,otherLocales(new)StoreProductresolvers:otherLocales(new),breadcrumbList(localized via Catalog Dataplane)Query.product: localized slug validation via Catalog Dataplane whenlocalization.enabledQuery.collection: acceptslocalearg, callsmutateLocaleContextfor correct SSG locale propagationStoreCollectionLocale,StoreProductLocaletypes;otherLocaleson bothStoreCollectionandStoreProduct@faststore/core[...slug].tsx: passeslocaleto collection query;LocalizedProductContext+useBindingSelector: page-agnostic locale switching withurlSuffix(empty for PLP,/pfor PDP)LocalizationButton: readsurlSuffixfrom contextTest plan
/apparel) — products load,selectedFacets: [{key:"category-1", value:"apparel"}]/apparel/shirts) — multi-level breadcrumb correct/adidas) —selectedFacets: [{key:"brand", value:"adidas"}]/computer---software) —selectedFacets: [{key:"productclusterids", value:"..."}]/it-IT/Abbigliamento) — resolves, no IS 500s, canonical facets sent/pt-BR/Vestuário/apparelotherLocalesdrivesLocalizationSelectorKnown limitations / follow-ups
by-linkidnamefield always returns the default-locale value. Feedback filed with the Catalog team to addavailableNamesorAccept-Languagesupport.availableLinkIdsexcludes the default locale: worked around via category tree lookup. Feedback filed with Catalog team to include the default locale in the map.metaTagDescriptionnot yet inby-linkidresponse: resolver already readsroot.metaTagDescription; will work once Catalog team ships the field. SEO description is currently empty for collection pages (pre-existing gap, not a regression from this PR).Related
Spec:
specs/localized-plp-collection-pages.mdPRPhase A (PDP): feat(core): localized product URLs, breadcrumbs and hreflang for PDP #3352
Reference: https://vtex-dev.atlassian.net/browse/SFS-3196
Summary by CodeRabbit