Skip to content

spec+docs: Content Platform CMS support (increment 1 of cms-routes-in-sitemap-for-faststore) - #189

Open
renatomaurovtex wants to merge 14 commits into
vtex-apps:masterfrom
renatomaurovtex:spec/content-platform-cms-support
Open

spec+docs: Content Platform CMS support (increment 1 of cms-routes-in-sitemap-for-faststore)#189
renatomaurovtex wants to merge 14 commits into
vtex-apps:masterfrom
renatomaurovtex:spec/content-platform-cms-support

Conversation

@renatomaurovtex

@renatomaurovtex renatomaurovtex commented May 27, 2026

Copy link
Copy Markdown

Summary

This PR is an increment on top of the cms-routes-in-sitemap-for-faststore spec/feature (PR #188). It adds support for the new VTEX CMS (the Content Platform) as a parallel route source to the existing hCMS (legacy) integration.

Stacked on: #188 (the hCMS rollout). This PR will show diff against the parent until #188 merges. After #188 lands on master, this branch will rebase cleanly.

Status of files touched:

  • specs/cms-routes-in-sitemap-for-faststore.md → Status changed from `Done (hCMS legacy)` to `Done (hCMS legacy) · Draft (Content Platform increment)`.
  • docs/CMS_ROUTES.md → Anticipates the new behavior (planned [email protected]), with mutual-exclusivity matrix, enable/verify/exclude flows and a step-by-step migration guide.

No code is shipped in this PR — only the spec and the customer-facing doc that anticipates it. Implementation lands in subsequent PRs (P6–P8 of the spec).

Why now

VTEX is progressively migrating Headless CMS (legacy) stores to the new VTEX CMS (Content Platform) since March 30, 2026. Without an explicit Content Platform path in this app, every store that flips loses its CMS-page coverage in the sitemap — defeating the purpose of the hCMS work already in #188.

What's in this PR

`specs/cms-routes-in-sitemap-for-faststore.md` (commit `2d1de61`)

The spec is woven to cover both CMS surfaces (not appended as a separate section). Key additions:

Area Increment
Assumptions Two CMS sources in scope (hCMS legacy + Content Platform), with mutual-exclusivity note.
Problem Statement Adds the migration-context paragraph (Mar 2026 announcement).
Goals Adds parity goal: same FastStore-side experience regardless of which CMS the store uses.
User Stories US-1 and US-2 split into per-source ACs; new US-6 for migrate-without-regression.
Key Scenarios 6 new scenarios (Content Platform happy paths, custom type with `path`, both-flags-on, Data Plane outage, branch isolation, locale fidelity, rollback).
FRs FR-1 / FR-2 scoped as hCMS-only; FR-3 generalized; new FR-9 (Content Platform ingestion), FR-10 (mutual exclusivity), FR-11 (opt-out via SEO fields).
NFRs ETag caching, locale fidelity, performance parity, new outbound-access policy.
Out of Scope Non-production branches, new schema toggle, parallel ingestion, content migration.
Architecture mermaid Updated with `generateContentPlatformRoutes` middleware and mutual-exclusivity gateway.
Alternatives 3 new rejected: CP via Rewriter, parallel with dedup, hardcoded allowlist.
Risks 6 new CP-specific risks with mitigations.
Decisions 7–11 Data Plane as parallel source · Mutual exclusivity (CP wins) · Schema-driven discovery · SEO-fields-only opt-out · Locale fidelity.
Implementation Plan Split into "hCMS track (shipped)" P1–P5 + "Content Platform increment (planned)" P6 / P7 / P8.
Technical Contract New `ContentPlatformRouteEntry`, `Route.source` field, `enableContentPlatformRoutes` setting, new `/sitemap/content-platform-routes-{n}.xml` endpoint, new `generateContentPlatformRoutes.ts` middleware and `cmsDataPlane.ts` client, new outbound-access policy, invariants 10–14.

`docs/CMS_ROUTES.md` (commit `aa5f07e`)

The customer-facing doc gains two new top-level sections:

  • Content Platform support — how-it-works, mutual-exclusivity matrix, enable via UI/CLI, verify steps, SEO-field opt-out, observability events.
  • Migrating from hCMS to Content Platform — step-by-step playbook (snapshot · flip flags · validate · spot-check · rollback) for stores being progressively upgraded by the CMS team.

The existing hCMS-specific content is clarified as legacy (with a pointer to the new section). Known Limitations and Reference sections are updated.

What's NOT in this PR

  • No code changes. Implementation (new `generateContentPlatformRoutes` middleware, `cmsDataPlane` client, settings/policy updates) ships in P6–P8 PRs.
  • No flag flips on existing stores. `enableContentPlatformRoutes` defaults to `false` once the code lands.

Test plan

This is a documentation-only PR. Validation is by review:

  • Spec is internally consistent (FR/Decision/Invariant cross-references resolve, mermaid renders).
  • Mutual-exclusivity behavior is unambiguous between FR-10, Decision 8, and the docs' matrix table.
  • Migration guide steps are accurate against the documented HTTP contract.
  • Open with the CMS team that the production-branch identifier, Data Plane host, and SEO-field opt-out semantics match their roadmap before P6 starts.

Related

Made with Cursor

renatomaurovtex and others added 14 commits May 27, 2026 12:25
Add SDD for SFS-3123 covering Business Context, Arch Decisions and
Technical Contract for including CMS/hCMS routes in the sitemap served
by vtex.store-sitemap for FastStore stores.

Co-authored-by: Cursor <[email protected]>
Per the spec for SFS-3123, introduce the rollout-gated `enableCmsRoutes`
setting (default `false`), the `CMS_ROUTES_INDEX` index filename, and
the protocol-driven `CMS_ROUTES_MAX_URLS_PER_FILE` / `*_MAX_BYTES_PER_FILE`
ceilings (50k URLs / 50 MB) that drive the chunking step.

The setting is wired into the existing INDEX_MAP so that `enabledIndexFiles`
includes the new bucket only when the flag is on, preserving backwards
compatibility for stores that have not opted in.

Co-authored-by: Cursor <[email protected]>
New middleware (`generateCmsRoutes`) that materializes the CMS-routes
sitemap source for FastStore stores per the spec for SFS-3123:

- Pulls Rewriter Internals via the existing `listInternalsWithRetry`
- Classifies routes as CMS-origin by excluding framework-owned types
  (`product`, `department`, `category`, `subcategory`, `brand`) and
  `notFound*` error pages
- Mandatorily excludes types containing `login` or `error` substrings
  (FR-3); hCMS metadata join (noindex / canonical) is deferred per
  Decision 3 — defensive default is "not excluded"
- Honors the CMS toggle via Rewriter `disableSitemapEntry`
- Honors the existing `disableRoutesTerm` substring exclusion
- Groups by binding and writes per-binding entry files plus the
  `CMS_ROUTES_INDEX` to a dedicated `cms-routes_<hash>` bucket
- Chunks each binding's routes at the protocol ceiling
  (CMS_ROUTES_MAX_URLS_PER_FILE / CMS_ROUTES_MAX_BYTES_PER_FILE)
- Short-circuits to a no-op when `enableCmsRoutes` is false so the
  rollout is reversible (invariant 9)

7 jest tests cover binding partitioning, exclusion filters (framework
types, disableSitemapEntry, notFound/login/error, disableRoutesTerm),
chunking at the URL ceiling, and the off-by-default short-circuit.

Co-authored-by: Cursor <[email protected]>
Add `getCmsRoutes` to `services/routes.ts` mirroring the shape of
`getUserRoutes` and `getAppsRoutes` (flat path list across bindings),
reusing the `isValidCmsRoute` filter exported from `generateCmsRoutes`
so the JSON API and the XML pipeline are guaranteed to apply the same
classification (invariant 6 — determinism).

Wire it into `generateCustomRoutes` so the VBase-cached payload returned
by `GET /_v/public/sitemap/custom-routes` now contains a third entry:

  { name: 'cms-routes', routes: ['/our-story', '/black-friday', ...] }

The `customRoutes` middleware filters the response based on both
`enableAppsRoutes` and `enableCmsRoutes` so each section disappears when
its corresponding rollout flag is off (invariant 9). Two new tests cover
the on/off filter cases.

Co-authored-by: Cursor <[email protected]>
… (US-3, FR-5)

Per Decision 5 of the spec, multi-locale stores now declare every locale
alternate (including self) and a `hreflang="x-default"` pointing at the
default binding for each `<url>` entry. Single-binding stores keep the
prior behavior and emit no alternates (preserves invariant 7 — backwards
compatibility).

Per FR-5, the `Route` type gains optional `changefreq`, `priority` and
`lastmod` fields. URLEntry emits the corresponding `<changefreq>` and
`<priority>` tags only when the route declares them, keeping existing
sources (product / navigation / apps) byte-compatible with their current
XML output. `generateCmsRoutes` writes routes with default values
(`weekly` / `0.5`) so every CMS-origin entry carries the protocol tags
as required.

Two existing localization tests are updated to the new spec-compliant
shape; four new tests cover single-binding suppression, current binding
not being the default, presence of `<changefreq>`/`<priority>` when set,
and their absence when omitted.

The 3 pre-existing failures in this test file are in the unrelated
`catalogSitemapEntry` path and not affected by this change.

Co-authored-by: Cursor <[email protected]>
…on 6)

The `robots` middleware now appends `Sitemap: https://{host}/sitemap.xml`
to the served body when no `Sitemap:` directive is already present. The
detection is case-insensitive and tolerant of leading whitespace
(invariant 8 of the spec — robots.txt idempotency), so merchants who
declared their sitemap manually are unaffected and the directive is
never duplicated.

Logic is extracted into a pure `ensureSitemapDirective` helper covered
by 7 unit tests: append on missing, no-op on present, case-insensitive
match, indented match, empty body, repeated-call idempotency, and the
"existing different URL" case (we honor the merchant's manual choice
rather than override it).

Co-authored-by: Cursor <[email protected]>
…back

`sitemap` middleware now reads the CMS routes index from the dedicated
`cms-routes_<bindingHash>` bucket and appends its entries to the served
`<sitemapindex>` when `enableCmsRoutes` is on (invariant 9). The read is
nullable — a missing CMS index simply means no extra sub-sitemaps to
emit, preserving the "no empty regression" invariant.

`sitemapEntry` middleware's legacy path now falls back to the cms-routes
bucket when the requested file is not found in the production bucket, so
that crawler requests for `/sitemap/cms-routes-N.xml` resolve correctly.

Two new tests in `sitemap.test.ts` cover the on/off rollout cases. As a
positive side effect of adding `isCrossBorder: true` to the shared mock
context (required to exercise the legacy path that my changes touch),
the 7 previously-failing tests in the suite that mis-routed to
`catalogSitemap` are also now green — they were testing the legacy
behavior all along but the default path had changed.

Co-authored-by: Cursor <[email protected]>
All five user stories are implemented and tested. Closing the loop on
the spec lifecycle.

Co-authored-by: Cursor <[email protected]>
New `docs/CMS_ROUTES.md` covering the full lifecycle of the CMS routes
feature introduced in SFS-3123: how it works, enabling via Admin UI or
CLI, smoke-test verification steps, per-page exclusion options (toggle,
Rewriter mutation, disableRoutesTerm), multi-locale hreflang output,
the JSON endpoint migration path from next-sitemap, robots.txt
integration, and a known-limitations table for the pending hCMS
cross-team dependencies.

`docs/README.md` gains a short "CMS routes (FastStore)" section that
cross-links to the new document.

Co-authored-by: Cursor <[email protected]>
The middleware existed and had unit tests but was never connected to the
pipeline: GENERATE_CMS_ROUTES_EVENT was missing, service.json had no
handler entry, index.ts did not register the event handler, and
generateSitemap never fired the event when enableCmsRoutes was on.

Co-authored-by: Cursor <[email protected]>
Stop routing CMS routes through enabledIndexFiles (wrong VBase bucket) and
group Rewriter internals by page id so persisted routes carry cross-binding
alternates for US-3 hreflang output.

Co-authored-by: Cursor <[email protected]>
Add support for the new VTEX CMS (Content Platform) as a parallel route
source to the existing hCMS (legacy) integration, woven across the existing
sections of the spec.

Highlights:
- New CMS source via the VTEX CMS Data Plane REST API (Decision 7), pinned
  to the production branch with ETag (`If-None-Match`) caching.
- Mutual exclusivity between hCMS and Content Platform per generation;
  Content Platform wins when both flags are set (Decision 8 / FR-10).
- Schema-driven content-type discovery: every type whose schema declares a
  `path` field is ingested, no hardcoded allowlist (Decision 9).
- Opt-out driven exclusively by existing SEO fields (`noindex`, `canonical`)
  with no new schema field required (Decision 10 / FR-11).
- Locale fidelity: alternates emitted only for actually-published locales,
  no synthetic fallback URLs (Decision 11).
- Phases P6, P7, P8 added to the Implementation Plan; planned for
  `[email protected]`.

Motivated by the [progressive migration of hCMS stores to the new VTEX
CMS][1] underway since March 2026, which would otherwise regress sitemap
coverage for every FastStore v3 store that flips to the new CMS.

[1]: https://help.vtex.com/announcements/2026-03-30-headless-cms-stores-will-be-upgraded-to-the-new-vtex-cms

Co-authored-by: Cursor <[email protected]>
…uide

Update CMS_ROUTES.md to reflect the Content Platform increment specified
in cms-routes-in-sitemap-for-faststore.md. The doc continues to be the
single source of truth for both CMS surfaces; the existing hCMS sections
are clarified as legacy and two new top-level sections are added:

- "Content Platform support" — how the new ingestion path works (Data
  Plane API, production-branch pinning, schema-driven discovery, SEO-field
  opt-out, ETag caching), mutual-exclusivity matrix between the two
  flags, enable/verify/exclude flows specific to Content Platform, and
  the observability events emitted by the new generator.
- "Migrating from hCMS to Content Platform" — step-by-step migration
  guide (snapshot, flip flags, validate, spot-check, rollback) for
  stores being progressively upgraded by the CMS team.

Known limitations and the Reference section are updated accordingly.

The new behavior ships in `[email protected]` (P6-P8 of the spec
Implementation Plan); this commit documents the contract ahead of code so
that customer-success and integrators can prepare in parallel.

Co-authored-by: Cursor <[email protected]>
@vtex-io-ci-cd

vtex-io-ci-cd Bot commented May 27, 2026

Copy link
Copy Markdown
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:

  • Patch (backwards-compatible bug fixes)

  • Minor (backwards-compatible functionality)

  • Major (incompatible API changes)

And then you just need to merge your PR when you are ready! There is no need to create a release commit/tag.

  • No thanks, I would rather do it manually 😞

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