Page model
Evolve uses two complementary page systems: CMS-managed pages for content-driven experiences and catalog pages for category browsing driven by the commerce backend.
CMS-managed pages
Section titled “CMS-managed pages”CMS-managed pages are created and maintained in the CMS (Contentful or
Storyblok). They share a common interface and are returned through a
Page union type:
interface AbstractPage { id: ID! name: String! path: String! translatedPaths: [TranslatedPath!] breadcrumbs: [Breadcrumb!] meta: PageMeta}
union Page = ContentPage | CatalogPageContentPage
Section titled “ContentPage”A free-form content page composed of blocks managed in the CMS. Use this for pages like “About us”, landing pages, or editorial content.
type ContentPage implements AbstractPage @key(fields: "id") { id: ID! name: String! path: String! translatedPaths: [TranslatedPath!] breadcrumbs: [Breadcrumb!] meta: PageMeta hero: HeroBlock body: [Block]}Query a content page by path:
query GetContentPage($path: String!) { contentPage(path: $path) { name meta { title, description } breadcrumbs { name, path, type } hero { ... } body { ... } }}CatalogPage
Section titled “CatalogPage”A product listing page where the content and product selection are defined in the CMS. The CMS service owns the page content (hero, top/bottom content sections, teasers), while the catalog service extends it with product listing configuration through GraphQL Federation.
# CMS service owns the content fieldstype CatalogPage implements AbstractPage @key(fields: "id") { id: ID! name: String! path: String! translatedPaths: [TranslatedPath!] breadcrumbs: [Breadcrumb!] meta: PageMeta hero: HeroBlock topContent: [Block!] bottomContent: [Block!] categoryId: String teaserRow: TeasersBlock}
# Catalog service extends with product listing configtype CatalogPage @extends @key(fields: "id") { id: ID! categoryId: String @external productListingConfig: ProductListingConfig! @requires(fields: "categoryId")}This is a good example of how GraphQL Federation works in Evolve: the CMS
service resolves the content fields, the catalog service resolves
productListingConfig by reading the categoryId that the CMS provides,
and the gateway merges both into a single response.
Query a catalog page by path:
query GetCatalogPage($path: String!) { catalogPage(path: $path) { name categoryId meta { title, description } breadcrumbs { name, path, type } productListingConfig { categoryId defaultOrder prefilters { key, value } } topContent { ... } bottomContent { ... } }}Generic page query
Section titled “Generic page query”When you don’t know the page type ahead of time, use the page query
which returns the Page union:
query GetPage($path: String!) { page(path: $path) { ... on ContentPage { name body { ... } } ... on CatalogPage { name productListingConfig { categoryId } } }}All three CMS page queries (contentPage, catalogPage, page) accept
optional version and previewID arguments for draft/preview content.
Catalog pages
Section titled “Catalog pages”Catalog pages are resolved directly from the commerce backend without CMS
involvement. They are standalone types (not part of the Page union) and
are owned entirely by the catalog service.
ProductCategoryPage
Section titled “ProductCategoryPage”Renders all products in a specific product category. The category hierarchy in the commerce backend is the source of truth.
type ProductCategoryPage { meta: PageMeta path: String! name: String indexable: Boolean category: ProductCategory! productListingConfig: ProductListingConfig! breadcrumbs: [Breadcrumb!]!}Query by path:
query { productCategoryPageByPath(path: "/shoes/running") { name category { name, slug } productListingConfig { categoryId defaultOrder enabledFacets } breadcrumbs { name, path } }}CategoryOverviewPage
Section titled “CategoryOverviewPage”Displays child categories for a parent category, useful for top-level navigation pages.
type CategoryOverviewPage { meta: PageMeta path: String! name: String indexable: Boolean category: ProductCategory childCategories: [ProductCategory!]! breadcrumbs: [Breadcrumb!]!}Product detail pages
Section titled “Product detail pages”Product detail pages don’t have a dedicated page type. The frontend
queries the product field directly:
query GetProductDetailPage($slug: String!) { product(slug: $slug) { name brand slug meta { title, description } breadcrumbs { name, path, type } variant(sku: $selectedSku) { sku availability description price { gross { centAmount, currencyCode } net { centAmount, currencyCode } } images { url, alt } } }}Search pages
Section titled “Search pages”Search pages also don’t have a dedicated page type. The frontend uses the
productSearch query from the catalog service:
query SearchProducts( $searchTerm: String $page: Int! $pageSize: Int! $sort: ProductSortOrder! $filters: [FacetFilterInput!]) { productSearch( searchTerm: $searchTerm page: $page pageSize: $pageSize sort: $sort filters: $filters ) { total items { ... } facets { ... } }}Shared types
Section titled “Shared types”Several types are shared across page types using the @shareable
federation directive:
type PageMeta @shareable { title: String description: String keywords: [String] openGraph: OpenGraph}
type Breadcrumb @shareable { type: BreadcrumbType! name: String! path: String!}
enum BreadcrumbType { CATEGORY CONTENT PRODUCT}Both the CMS and catalog services resolve PageMeta and Breadcrumb
independently, with the @shareable directive telling the gateway that
either service can provide these types.

