feat: Content Platform CMS routes in sitemap (Increment 2 of cms-routes-in-sitemap-for-faststore) - #190
Open
renatomaurovtex wants to merge 2 commits into
Conversation
Contributor
|
Hi! I'm VTEX IO CI/CD Bot and I'll be helping you to publish your app! 🤖 Please select which version do you want to release:
And then you just need to merge your PR when you are ready! There is no need to create a release commit/tag.
|
|
Beep boop 🤖 Thank you so much for keeping our documentation up-to-date ❤️ |
Wire Headless CMS and VTEX CMS (Content Platform) as parallel, mutually exclusive sitemap sources for FastStore stores: generation middlewares, VBase persistence, custom-routes JSON, legacy and single-binding catalog serving paths, hreflang alternates, and rollout settings. Content Platform ingests via the real Data Plane API (cursor listing + per-locale getEntry, canonical opt-out, ETag cache). hCMS sources FastStore pages from the CMS Builder API (not Rewriter) under hcms-routes-N.xml; Content Platform uses cms-routes-N.xml. Consolidate CMS source resolution (cmsSources registry), shared eligibility and serving helpers, HTTP retry for CMS clients, and centralized mutual- exclusivity logging in generateSitemap. Co-authored-by: Cursor <[email protected]>
renatomaurovtex
force-pushed
the
feat/content-platform-cms-support
branch
from
May 29, 2026 13:21
b594d58 to
925a532
Compare
When the legacy sitemap had no data yet, SitemapNotFound was caught, generation was triggered, but `throw err` at the end of the catch block unconditionally re-threw the error — causing the VTEX IO framework to respond with 500 instead of the intended 404. Adding `return` after the SitemapNotFound handler short-circuits the re-throw. Co-authored-by: Cursor <[email protected]>
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.
Summary
Implements the Content Platform increment (P6–P9) of
specs/cms-routes-in-sitemap-for-faststore.md, wiring the new VTEX CMS (Content Platform) Data Plane REST API as a parallel route source alongside the existing hCMS/Rewriter integration. Builds on PRs #187 (spec), #188 (hCMS implementation) and #189 (Content Platform spec increment).Real Data Plane contract used
https://{account}.vtexcommercestable.com.br/api/content-platform/data/*branchparameter on the REST surface.contentPlatformContentTypes) rather than discovered. Auto-discovery viavtex.admin-cms-graphqlis documented as a follow-up.listEntries(cursor-paginated, returnsid/name/updatedAt/searchKeywords, noslug) andgetEntry(returns fullseo.slug,seo.canonical,seo.title, locale metadata).getEntryBySlugis also exposed on the client for ad-hoc lookups but not used by the sitemap path.getEntryis called once per(entry, binding.defaultLocale). The Data Plane returns404 Not Foundfor locales that were never published — the binding is silently skipped, so we never synthesise locales (Decision 5).ETagheaders and honorsIf-None-Match/304 Not Modified. Cached payloads are persisted in VBase across generations.hCMS (legacy) source — FastStore correction
FastStore Headless CMS pages live in the CMS builder data layer and are delivered to the storefront via the REST API (
/_v/cms/api/{projectId}/{contentType}) — they are never registered as Rewriter Internals. The previousgenerateCmsRoutesread fromrewriter.listInternals, so hCMS landing pages of FastStore stores never reached the sitemap. ACmsBuilderclient + sharedfetchEligibleHcmsSlugshelper now source these pages directly (published-only, requiresseo.slug, excludes home/, honorsseo.canonicalopt-out anddisableRoutesTerm); project id and routable content types are settings-driven (hcmsProjectId,hcmsContentTypes).Per user story
generateContentPlatformRoutesiteratescontentPlatformContentTypes(default["landingPage", "home"]), paginateslistEntries, and fans outgetEntrycalls per binding-locale. Routes are emitted fromseo.slug.seo.canonicalis non-empty AND points to a URL different from its ownseo.slug(Decision 10).noindexis not available in the current Content Platformseoschema, so the opt-out is canonical-only for now. Login/error paths are excluded centrally (FR-3).xhtml:linktags for every locale where the entry actually returned200, plus anx-default. Bindings whosegetEntryreturns404are silently dropped — fallback locales are never synthesised./_v/public/sitemap/custom-routesexposes acontent-platform-routes(orcms-routes) section. Mutual exclusivity is enforced at serve time: only the active source's section is returned.resolveActiveCmsSource(settings)returns'hcms' | 'content-platform' | 'none'. When both flags are on, Content Platform wins (Decision 8) andcms-routes-ignored-by-mutual-exclusivityis logged once per generation. Enforced at four layers: event firing, generation middlewares, served<sitemapindex>/ entry files, andcustomRoutes.Single-binding serving (catalog proxy path)
Single-binding stores (the common FastStore case, e.g.
isCrossBorder === false) delegate both/sitemap.xmland/sitemap/:pathto the catalog proxy (catalog.getSitemap), which never reads VBase. As a result the generated CMS routes were correctly persisted but invisible in the served XML —/sitemap.xmlonly listedbrand/category/product, and/sitemap/cms-routes-N.xmlreturned400(the catalog has no such file). Only multi-binding (legacySitemap) stores and thecustom-routesJSON endpoint surfaced them.This increment closes that gap on the catalog path:
catalogSitemap(innode/middlewares/sitemap.ts) now reads the active CMS index from VBase (getCmsIndexFor) and merges its sub-sitemap entries into the catalog<sitemapindex>, with a defensive fallback (catalog XML returned unchanged when it has no<sitemapindex>or no CMS index exists).catalogSitemapEntry(innode/middlewares/sitemapEntry.ts) now serves CMS entry files (/sitemap/cms-routes-N.xml,/sitemap/hcms-routes-N.xml) directly from the active CMS bucket in VBase before falling back to the catalog proxy.resolveActiveCmsSource(mutual exclusivity) and are fully gated by the existing settings — behavior is unchanged when no CMS source is enabled. Single-binding entries carry noxhtml:linkalternates (preserved viabuildLocalization).Key code additions
node/clients/cmsDataPlane.ts— external client againstvtexcommercestable.com.br/api/content-platform/data/*with cursor-basedlistEntries,getEntry(withIf-None-Match),getEntryBySlug, 5xx retry, and graceful404handling ({ notFound: true }).node/clients/cmsBuilder.ts— external client against{account}.myvtex.com/_v/cms/api/*for the FastStore hCMS legacy source (paginatedlistPages, 5xx retry).node/services/hcmsRoutes.ts— sharedfetchEligibleHcmsSlugs/isEligibleHcmsPagefilter used by both the XML generator and thecustom-routesendpoint (determinism, invariant 6).node/middlewares/generateMiddlewares/generateContentPlatformRoutes.ts— settings-driven ingestion, per-locale fan-out, canonical-based SEO opt-out,disableRoutesTermfiltering, multi-locale alternates, chunked persistence (50k URLs / 50 MB per file — Decision 2) under a dedicatedcontent-platform-routesVBase bucket with its own index (Decision 7).node/services/contentPlatformCache.ts— VBase-backed ETag cache (content-platform-data-cachebucket) with base64url-safe filenames keyed by(contentType, entryId, locale).node/services/routes.ts—resolveActiveCmsSource+resolveCmsBucket+getContentPlatformRoutes;getCmsRoutesearly-returns when hCMS is not active and now sources from the CMS builder API.node/middlewares/{sitemap,sitemapEntry,customRoutes}.tsandgenerateMiddlewares/{generateSitemap,generateCustomRoutes,generateCmsRoutes}.ts— wire the active-source resolver through both pipelines, including the single-binding catalog path (see above).node/globals.ts—Route.source: 'hcms' | 'content-platform' | 'apps' | 'user'.manifest.json—enableContentPlatformRoutes(boolean, defaultfalse),contentPlatformStoreId(string, default"faststore"),contentPlatformContentTypes(string[], default["landingPage", "home"]),hcmsProjectId/hcmsContentTypes, and outbound-access policies for{{account}}.vtexcommercestable.com.br/api/content-platform/data/*and{{account}}.myvtex.com/_v/cms/api/*.Tests
generateContentPlatformRoutes.test.ts— settings allowlist honored / falls back to default, multi-locale fan-out emits one URL per actually-published locale with grouped alternates, 404 silently skips unpublished locales (no synthesised alternates), single-binding stores write only the en-US bucket, ETag is persisted on fresh200, cachedPublishedEntryis reused on304,seo.canonical-based exclusion, login/error exclusion,disableRoutesTermfilter, mutual-exclusivity logging.generateCmsRoutes.test.ts— hCMS pages sourced from the CMS builder API: published-only, canonical opt-out,disableRoutesTerm, chunking, off-by-default and mutual-exclusivity short-circuits.node/services/routes.test.ts— exhaustive matrix forresolveActiveCmsSource(all flag combinations, Content Platform tie-breaker).sitemap.test.ts/sitemapEntry.test.ts— single-binding catalog path: index merge for hCMS and Content Platform, catalog XML unchanged when no CMS source is enabled, mutual exclusivity (only the active source merged), entry served from VBase before the catalog fallback, and404/proxy fallback when the CMS file is absent or the flag is off.generateSitemap.test.ts,customRoutes.test.tscover event firing, bucket reads, and mutual exclusivity at every layer.Assumptions
contentPlatformStoreId/hcmsProjectIddefault to"faststore", matching the typical FastStorecontentSource.project. Stores using a different project must override the setting before enabling the flag.disableRoutesTermsetting (already used by hCMS) applies identically to Content Platform paths — same mental model for merchants migrating between sources.Deviations from spec (carried into the spec text)
contentPlatformContentTypes); auto-discovery viavtex.admin-cms-graphqlis a follow-up.noindexis not in the current Content Platformseoschema). The originalnoindex OR canonical-mismatchrule is preserved in the decision text for traceability and will be re-enabled when the field is added.These changes are reflected in
specs/cms-routes-in-sitemap-for-faststore.md(Decisions 7/9/10/11 + Implementation Plan P6–P9) and indocs/CMS_ROUTES.md.Validation notes
vendemo): withenableCmsRouteson,/sitemap.xmlnow lists…/sitemap/cms-routes-0.xmlalongside the catalog entries, and/sitemap/cms-routes-0.xmlserves the hCMS landing page.generateCmsRoutes/generateContentPlatformRoutesmiddlewares run as event handlers. In a flakyvtex linkdev environment the event server can drop these events (Connection to event server has failed), leaving the CMS bucket empty and the serving path falling back to the catalog proxy. This is a dev-link limitation, not a serving bug — thecustom-routesJSON endpoint (which generates synchronously) is unaffected, and events work normally on a deployed app.Follow-ups
noindexopt-out — re-enable as a second leg of the SEO opt-out as soon as the Content Platformseoschema exposes the field.vtex.admin-cms-graphqlto auto-populatecontentPlatformContentTypesinstead of forcing a manual setting.utils.test.ts,prepare.test.ts,generateRewriterRoutes.test.ts) were already red onmaster— out of scope here, worth a separate cleanup PR.Spec & related PRs
specs/cms-routes-in-sitemap-for-faststore.md— status updated fromApproved (Content Platform increment)toDonein this PR.Made with Cursor