Skip to content

New: AI agent integration via Model Context Protocol (MCP).Learn more

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 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 | CatalogPage

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 { ... }
}
}

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 fields
type 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 config
type 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 { ... }
}
}

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 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.

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 }
}
}

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 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 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 { ... }
}
}

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.